对大文件大小的顺序 i/o 操作进行优化

Posted

技术标签:

【中文标题】对大文件大小的顺序 i/o 操作进行优化【英文标题】:optimization of sequential i/o operations on large file sizes 【发布时间】:2013-04-12 13:48:50 【问题描述】:
编译器:Microsoft C++ 2005硬件:AMD 64 位 (16 GB)

提交对 18GB 文件的顺序只读访问,具有以下时间、文件访问和文件结构特征:18,184,359,164(文件长度)11,240,476,672(ntfs 压缩文件长度)

时间文件方法磁盘
14:33?压缩的 fstream 固定磁盘
14:06 正常fstream固定盘
12:22 正常winapi固定盘
11:47 压缩winapi固定盘
11:29 压缩的 fstream ram 磁盘
10:37 压缩的 winapi ram 磁盘
 7:18 压缩 7z 存储解压到 ntfs 12gb ram 磁盘
 6:37 正常复制到同卷硬盘

fstream 构造函数和访问:

定义缓冲区大小 524288 无符号整数兆字节 = 缓冲区大小; 字符 * 数据缓冲区0; databuffer0 = (char*) malloc (mbytes); datafile.open("drv:/file.ext", ios::in | ios::binary ); datafile.read (databuffer0, mbytes); winapi构造函数及访问:
定义缓冲区大小 524288
    无符号整数兆字节 = 缓冲区大小;
    const TCHAR* const filex = _T("drv:/file.ext");
    字符 ReadBuffer[BUFFERSIZE] = 0;
    hFile = CreateFile(filex, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    如果(假 == ReadFile(hFile,ReadBuffer,BUFFERSIZE-1,&dwBytesRead,NULL))
     ...
对于 fstream 方法,-> 16MB 缓冲区大小不会减少处理时间。对于 winapi 方法,超过 .5MB 的所有缓冲区大小均失败。哪些方法可以优化此实现与处理时间?

【问题讨论】:

【参考方案1】:

您是否尝试过对文件进行内存映射?在我的测试中,这始终是读取大文件的最快方法。

更新:以下是对内存映射文件的旧但仍然准确的描述: http://msdn.microsoft.com/en-us/library/ms810613.aspx

【讨论】:

根据 ramdisk 实现的系统开销是否如此违规以至于内存映射顺序访问的文件具有更长的访问时间?看着数据源从固定磁盘更改为 ramdisk,我曾预计处理时间会减少 10 倍……这最终是通过使用大块读取来实现的,而不是使用在线内存。 Windows 操作系统中的某个地方是否有节流机制? ...?此外,内存映射文件需要一层用户编写的 (?) 压缩,以使 18GB 文件适合 12GB (16GB) 可用系统内存... 内存映射与缓存和内存子系统一起工作。这使得更好的优化成为可能。您可以映射大于物理内存的文件,无需压缩。操作系统将在读取时从文件中请求换入/换出页面。 不,没有节流机制(默认启用)。您可以在 Windows 上限制 IO,但您必须专门要求/实施它。【参考方案2】:

试试这个。

hf = CreateFile(..... FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED ...)

然后是阅读循环。在 iPad 上打字时省略了一些小细节...

int bufsize =4*1024*1024;
CEvent e1;
CEvent e2;
CEvent e3;
CEvent e4;
unsigned char* pbuffer1 = malloc(bufsize);
unsigned char* pbuffer2 = malloc(bufsize);
unsigned char* pbuffer3 = malloc(bufsize);
unsigned char* pbuffer4 = malloc(bufsize);
int CurOffset = 0;

do 
   OVERLAPPED r1;
   memset(&r1, 0, sizeof(OVERLAPPED));
   r1.Offset = CurOffset;
   CurOffset += bufsize;
   r1.hEvent = e1;
   if (! ReadFile(hf, pbuffer1, bufsize, bufsize, &r1)) 
       // check for error AND error_handle_eof (important)
   

   OVERLAPPED r2;
   memset(&r2, 0, sizeof(OVERLAPPED));
   r2.Offset = CurOffset;
   CurOffset += bufsize;
   r2.hEvent = e2;
   if (! ReadFile(hf, pbuffer2, bufsize, bufsize, &r2)) 
       // check for error AND error_handle_eof (important)
   

   OVERLAPPED r3;
   memset(&r3, 0, sizeof(OVERLAPPED));
   r3.Offset = CurOffset;
   CurOffset += bufsize;
   r3.hEvent = e3;
   if (! ReadFile(hf, pbuffer3, bufsize, bufsize, &r3)) 
       // check for error AND error_handle_eof (important)
   

   OVERLAPPED r4;
   memset(&r4, 0, sizeof(OVERLAPPED));
   r4.Offset = CurOffset;
   CurOffset += bufsize;
   r4.hEvent = e4;
   if (! ReadFile(hf, pbuffer1, bufsize, bufsize, &r4)) 
       // check for error AND error_handle_eof (important)
   

   // wait for events to indicate data present
   // send data to consuming threads
   // allocate new buffer
 while ( not eof, etc )

以上是您需要的骨骼。我们使用它来实现高 I/O 吞吐率,但您可能需要稍微改进它以实现最终性能。我们发现 4 个出色的 I/O 最适合我们使用,但这会因平台而异。每个 IO 读取不到 1Mb 对性能不利。读取缓冲区后,不要在读取循环中使用并使用它,发布到另一个线程并分配另一个缓冲区(但从重用队列中获取它们,不要继续使用 malloc)。上面的总体意图是尽量保持 4 个未完成的 IO 对磁盘开放,一旦你没有这个,整体性能就会下降。

此外,这在仅读取文件的磁盘上效果最佳。如果您同时开始在同一个磁盘上读取/写入不同的文件,性能会迅速下降,除非您有 SSD 磁盘!

不确定为什么您的 readfile 因 0.5Mb 缓冲区而失败,只是仔细检查了一下,我们的实时产品代码正在使用 4Mb 缓冲区

【讨论】:

以上是关于对大文件大小的顺序 i/o 操作进行优化的主要内容,如果未能解决你的问题,请参考以下文章

带有流的文件 I/O - 最佳内存缓冲区大小

流与缓冲

在 django 中使用“ImageKit”对已关闭文件进行 I/O 操作

APUE读书笔记-05标准输入输出库(1)

Unix系统调用

I/O流