CreateFileMapping、MapViewOfFile,如何避免占用系统内存

16

我正在开发一个面向桌面系统的应用程序,其中可能只有256MB RAM(适用于Windows 2000及以上版本)。在我的应用程序中,我有一个大文件(> 256MB),其中包含约160个字节/每个的固定记录。该应用程序具有相当长的过程,在此期间,它将随机访问文件约90%的内容(进行读写操作)。任何给定的记录写入将不会超过离特定记录读取1,000个记录访问之远(我可以调整此值)。

对于这个过程,我有两个明显的选项:常规I/O(FileRead,FileWrite)和内存映射(CreateFileMapping,MapViewOfFile)。在具有足够内存的系统中,后者应该更为高效,但在内存较低的系统中,它会换出大部分其他应用程序的内存,在我的应用程序中是不允许的。是否有一种方法可以防止该过程占用所有内存(例如,像强制刷新我不再访问的内存页)?如果这不可能,那么我必须回到常规I/O。我本来想使用重叠I/O进行写入部分(因为访问如此随机),但文档说,小于64K的写入始终是同步服务

欢迎任何改进I/O的想法。


也许VirtualFree(MEM_DECOMMIT)可以帮助解决问题?我不太熟悉它。 - Guillermo Prandi
1
不,VirtualFree(MEM_DECOMMIT)对于MMFs失败了;我刚刚检查过。 - Guillermo Prandi
我们提供给CreateFileMapping的文件偏移参数是否转化为map object消耗的内存量?我不太明白为什么这个偏移量会变成map object的大小。我们对这个偏移量之前的字节不感兴趣。(除了由于粒度而产生的一些小片段。) - daparic
4个回答

17

我终于找到了一种方法,这个方法来源于一个帖子。诀窍在于对需要取消分配的范围使用VirtualUnlock (); 尽管此函数返回错误0x9e(“段已解锁”),但实际上释放了内存,即使页面被修改(文件正确更新)。

这是我的示例测试程序:

#include "stdafx.h"

void getenter(void)
{
    int     ch;
    for(;;)
    {
        ch = getch();
        if( ch == '\n' || ch == '\r' ) return;
    }
}

int main(int argc, char* argv[])
{
    char*   fname = "c:\\temp\\MMFTest\\TestFile.rar";      // 54 MB
    HANDLE  hfile = CreateFile( fname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL );
    if( hfile == INVALID_HANDLE_VALUE )
    {
        fprintf( stderr, "CreateFile() error 0x%08x\n", GetLastError() );
        getenter();
        return 1;
    }

    HANDLE map_handle = CreateFileMapping( hfile, NULL, PAGE_READWRITE | SEC_RESERVE, 0, 0, 0);
    if( map_handle == NULL )
    {
        fprintf( stderr, "CreateFileMapping() error 0x%08x\n", GetLastError() );
        getenter();
        CloseHandle(hfile);
        return 1;
    }

    char* map_ptr = (char*) MapViewOfFile( map_handle, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0 );
    if( map_ptr == NULL )
    {
        fprintf( stderr, "MapViewOfFile() error 0x%08x\n", GetLastError() );
        getenter();
        CloseHandle(map_handle);
        CloseHandle(hfile);
        return 1;
    }

    // Memory usage here is 704KB
    printf("Mapped.\n"); getenter();

    for( int n = 0 ; n < 10000 ; n++ )
    {
        map_ptr[n*4096]++;
    }

    // Memory usage here is ~40MB
    printf("Used.\n"); getenter();

    if( !VirtualUnlock( map_ptr, 5000 * 4096 ) )
    {
        // Memory usage here is ~20MB
        // 20MB already freed!
        fprintf( stderr, "VirtualUnlock() error 0x%08x\n", GetLastError() );
        getenter();
        UnmapViewOfFile(map_ptr);
        CloseHandle(map_handle);
        CloseHandle(hfile);
        return 1;
    }

    // Code never reached
    printf("VirtualUnlock() executed.\n"); getenter();

    UnmapViewOfFile(map_ptr);
    CloseHandle(map_handle);
    CloseHandle(hfile);

    printf("Unmapped and closed.\n"); getenter();

    return 0;
}

正如您所看到的,执行VirtualUnlock()后程序的工作集被减少了,这正是我所需要的。我只需要跟踪我更改的页面以便适当地解锁。


太好了,感谢您分享这个。我一直在绝望地寻找一种控制内存映射文件使用量的方法,我已经失去了希望,认为这是不可能完成的。我只希望微软不会在未来的操作系统中禁用它。毕竟,我们为什么不能根据需要提交和取消提交页面呢? - Suma
注意:根据VirtualUnlock文档备注,您描述的错误代码似乎是预期的:“如果指定范围中的任何页面未被锁定,则VirtualUnlock将这些页面从工作集中删除,将最后一个错误设置为ERROR_NOT_LOCKED,并返回FALSE。” - Suma
还有一点需要注意:这个实验有些误导人。进程的工作集被减少了,但这并不一定意味着页面被丢弃了。如果您再次映射它,您会发现可以立即获取到它,而不需要进行任何页面文件活动。请参见我的实验:http://stackoverflow.com/questions/3525202/how-can-i-decommit-a-file-mapped-page/3525266#3525266 - Suma

3

只需将整个文件映射到内存中即可。这会消耗虚拟内存而非物理内存。文件被分段从磁盘读取,并按照控制交换文件的相同策略从内存中清除。


4
那正是问题所在。控制交换文件的策略倾向于将其内容保留在内存中,比文件缓存的内容更多,因此访问映射文件最终会交换出大部分其他进程。 - Guillermo Prandi

2

VirtualUnlock似乎无法正常工作。您需要在UnmapViewOfFile(map_ptr)之前立即调用FlushViewOfFile(map_ptr,0)。

Windows任务管理器将不会显示物理内存使用情况。请使用SysInternals的ProcessExplorer。


1
你是否正在使用MapViewOfFile将整个文件映射为一个块?如果是的话,请尝试将其分成较小的部分进行映射。你可以使用FlushViewOfFile()来刷新视图。

我无法按顺序访问文件;访问文件的模式是随机的,我无法控制,因此映射它的小部分将非常低效。此外,FlushViewOfFile()不会释放任何内存;只强制写入脏页。 - Guillermo Prandi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接