1

我今天一直在 VC++ 2008 上玩内存映射,但我仍然没有完全理解如何使用它,或者它是否适合我的目的。我的目标是快速读取一个非常大的二进制文件。

我有一个结构:

typedef struct _data
{
    int number;
    char character[512];
    float *entries;
}Data;

它被多次写入文件。“条目”变量是浮点小数数组。写完这个文件(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 个数据结构条目的数组中。当然,我这样做可能完全错了......

4

1 回答 1

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;
}

在将数据从结构写入内存时,您可能还想了解数据对齐。检查使用打包结构C++ 结构对齐问题数据结构对齐

如果您在读取时不提前知道数据的大小,则应将数据的大小写入内存映射开头的已知位置以供以后使用。

无论如何,为了解决它是否正确在这里使用它的事实,我认为它是正确的。来自维基百科

内存映射文件的主要好处是提高 I/O 性能,尤其是在用于大文件时。...内存映射过程由虚拟内存管理器处理,虚拟内存管理器与负责处理页面文件的子系统相同。内存映射文件一次加载一整页到内存中。页面大小由操作系统选择以获得最佳性能。由于页面文件管理是虚拟内存系统中最关键的元素之一,因此将文件的页面大小部分加载到物理内存中通常是非常高度优化的系统功能。

您要将整个内容加载到虚拟内存中,然后操作系统可以根据需要为您将文件分页进出内存,从而创建“延迟加载”机制。

综上所述,内存映射是共享的,因此如果它跨越进程边界,您将希望将它们与命名互斥锁同步,这样您就不会覆盖进程之间的数据。

于 2012-10-08T23:34:35.697 回答