我今天在VC ++ 2008上一直在玩内存映射,但我还没有完全理解如何使用它,或者它是否符合我的目的。我的目标是快速读取一个非常大的二进制文件。
我有一个结构:
typedef struct _data
{
int number;
char character[512];
float *entries;
}Data;
多次写入文件。 “entries”变量是浮点小数的数组。写完这个文件(10000个数据结构,每个“条目”数组为90000个浮点数)之后,我尝试使用以下函数对此文件进行内存映射,以便我可以更快地读取数据。这是我到目前为止所做的:
void readDataMmap(char *fname, //name of file containing my data
int arraySize, //number of values in struct Data
int entrySize) //number of values in each "entries" array
{
//Read and mem map the file
HANDLE hFile = INVALID_HANDLE_VALUE;
HANDLE hMapFile;
char* pBuf;
int fd = open(fname, O_RDONLY);
if(fd == -1){
printf("Error: read failed");
exit(-1);
}
hFile = CreateFile((TCHAR*)fname,
GENERIC_READ, // open for reading
0, // do not share
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no template
if (hFile == INVALID_HANDLE_VALUE)
{
printf("First CreateFile failed"));
return (1);
}
hMapFile = CreateFileMapping(hFile,
NULL, // default security
PAGE_READWRITE,
0, // max. object size
0, // buffer size
NULL); // name of mapping object
if(hMapFile == ERROR_FILE_INVALID){
printf("File Mapping failed");
return(2);
}
pBuf = (char*) MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_READ, // read/write permission
0,
0,
0); //Was NULL, 0 should represent full file bytesToMap size
if (pBuf == NULL)
{
printf("Could not map view of file\n");
CloseHandle(hMapFile);
return 1;
}
//Allocate data structure
Data *inData = new Data[arraySize];
for(int i = 0; i<arraySize; i++)inData[i].entries = new float[entrySize];
int pos = 0;
for(int i = 0; i < arraySize; i++)
{
//This is where I'm not sure what to do with the memory block
}
}
在函数结束时,在映射内存并且我返回指向内存块“pBuf”开头的指针后,我不知道该怎么做才能读回这个内存块进入我的数据结构。所以最终我想将这块内存转移回10000个数据结构条目的数组中。当然,我可能完全错了......
答案 0 :(得分:7)
处理内存映射文件与处理任何其他类型的内存指针没什么不同。内存映射文件只是一个数据块,您可以使用相同的名称从任何进程读取和写入。
我假设您要将文件加载到内存映射中,然后随意读取并更新它并将其以某个常规或已知间隔转储到文件中吗?如果是这种情况,那么只需从文件中读取并将数据复制到内存映射指针即可。稍后您可以从地图中读取数据并将其转换为与内存对齐的结构,并随意使用您的结构。
如果我是你,我可能会创建一些辅助方法,如
data ReadData(void *ptr)
和
void WriteData(data *ptrToData, void *ptr)
其中*ptr
是内存映射地址,*ptrToData
是指向数据结构写入内存的指针。真的在这一点上,无论是否映射了内存都无关紧要,如果你想从加载到本地内存中的文件读取,你也可以这样做。
您可以使用memcpy将数据从源复制到目标,以与使用任何其他块数据完全相同的方式读取/写入,并且可以使用指针算法来提升数据中的位置。不要担心“内存映射”,它只是指向内存的指针,你可以这样对待它。
此外,由于您将要处理直接内存指针,您不需要逐个将每个元素写入映射文件,您可以将它们全部写在一个批处理中,如
memcpy(mapPointer, data->entries, sizeof(float)*number)
将data->entries
的* *条目大小复制到地图指针起始地址。显然你可以随意复制它,无论你想要什么,这只是一个例子。请参阅http://www.devx.com/tips/Tip/13291。
要以相似的方式读取数据,但是您希望明确地将内存地址复制到已知位置,因此请想象展平您的结构。而不是
data:
int
char * -> points to some address
float * -> points to some address
如果您的指针指向其他地方的其他内存,请像这样复制内存
data:
int
char * -> copy of original ptr
float * -> copy of original ptr
512 values of char array
number of values of float array
因此,您可以将内存映射中的数据“重新序列化”到本地。请记住,数组只是指向内存的指针。内存不必在对象中是顺序的,因为它可能已在另一时间分配。您需要确保将指针指向的实际数据复制到内存映射中。执行此操作的常用方法是将对象直接写入内存映射,然后使用所有展平的数组跟踪对象。首先读取对象,然后将指针递增sizeof(object)
并读入下一个数组,然后再按arraysize
等递增指针。
以下是一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct data{
int size;
char items[512];
float * dataPoints;
};
void writeToBuffer(data *input, char *buffer){
int sizeOfData = sizeof(data);
int dataPointsSize = sizeof(float) * input->size;
printf("size of data %d\n", sizeOfData);
memcpy(buffer, input, sizeOfData);
printf("pointer to dataPoints of original %x\n", input->dataPoints);
memcpy(buffer + sizeOfData, input->dataPoints, dataPointsSize);
}
void readFromBuffer(data *target, char * buffer){
memcpy(target, buffer, sizeof(data));
printf("pointer to datapoints of copy %x, same as original\n", target->dataPoints);
// give ourselves a new array
target->dataPoints = (float *)malloc(target->size * sizeof(float));
// do a deep copy, since we just copied the same pointer from
// the previous data into our local
memcpy(target->dataPoints, buffer + sizeof(data), target->size * sizeof(float));
printf("pointer to datapoints of copy %x, now it's own copy\n", target->dataPoints);
}
int main(int argc, char* argv[])
{
data test;
for(unsigned int i=0;i<512;i++){
test.items[i] = i;
}
test.size = 10;
// create an array and populate the data
test.dataPoints = new float[test.size];
for(unsigned int i=0;i<test.size;i++){
test.dataPoints[i] = (float)i * (1000.0);
}
// print it out for demosntration
for(unsigned int i=0;i<test.size;i++){
printf("data point value %d: %f\n", i, test.dataPoints[i]);
}
// create a memory buffer. this is no different than the shared memory
char * memBuffer = (char*)malloc(sizeof(data) + 512 + sizeof(float) * test.size + 200);
// create a target we'll load values into
data test2;
// write the original out to the memory buffer
writeToBuffer(&test, memBuffer);
// read from the memory buffer into the target
readFromBuffer(&test2, memBuffer);
// print for demonstration
printf("copy number %d\n", test2.size);
for(int i=0;i<test2.size;i++){
printf("\tcopy value %d: %f\n", i, test2.dataPoints[i]);
}
// memory cleanup
delete memBuffer;
delete [] test.dataPoints;
return 0;
}
在将结构中的数据写入内存时,您可能还想要读取数据对齐。查看working with packing structures,C++ struct alignment question和data structure alignment。
如果您在阅读时未提前知道数据的大小,则应将数据大小写入内存映射开头的已知位置,以备日后使用。
无论如何,为了解决它是否正确使用它的事实,我认为它是。来自wikipedia
内存映射文件的主要好处是提高了I / O性能,尤其是在大文件上使用时。 ...内存映射过程由虚拟内存管理器处理,虚拟内存管理器是负责处理页面文件的相同子系统。内存映射文件一次一页地加载到内存中。操作系统选择页面大小以获得最佳性能。由于页面文件管理是虚拟内存系统最重要的元素之一,因此将文件的页面大小部分加载到物理内存中通常是一个非常高度优化的系统功能。
您将把整个内容加载到虚拟内存中,然后操作系统可以根据需要为您提供内存和内存页面,从而创建一个“延迟加载”机制。
所有这一切都说,内存映射是共享的,因此如果它跨越进程边界,您将需要将它们与命名的互斥锁同步,这样就不会在进程之间覆盖数据。