在Windows中fwrite比WriteFile快吗?

时间:2013-01-12 04:47:34

标签: c++ performance winapi file-io fwrite

我一直认为WriteFile比fwrite更有效,因为fwrite在内部调用WriteFile,但是下面的测试代码告诉我fwrite比WriteFile更快。

fwrite成本为2毫秒,而WriteFile需要27000(FILE_ATTRIBUTE_NORMAL),每次写入调用后都会刷新。如果我用FILE_FLAG_WRITE_THROUGH调用WriteFile,并注释FlushFileBuffers(wfile)行,WriteFile会更快,成本为800.

那真的是fwrite调用WriteFile吗?是什么让这么大的差异? fwrite如何在内部工作?如何使用API​​比fwrite更有效地将数据写入文件?(unbufferd,synchronous)。

   #include <Windows.h>
   #include <stdio.h>
   #include <iostream>

   int main() {
     FILE* cfile = fopen("file1.txt", "w");
     HANDLE wfile = CreateFile("file2.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 
           /*FILE_ATTRIBUTE_NORMAL*/FILE_FLAG_WRITE_THROUGH, NULL);
     DWORD written = 0;

     DWORD start_time, end_time;
     char * text = "test message ha ha ha ha";
     int size = strlen(text);
     int times = 999;

     start_time = timeGetTime();
     for(int i = 0; i < times; ++i) {
       fwrite(text, 1, size, cfile);
       fflush(cfile);
     }
     end_time = timeGetTime();
     std::cout << end_time - start_time << '\n';

     start_time = timeGetTime();
     for(int i = 0; i < times; ++i) {
         WriteFile(wfile, text, size, &written, NULL);
         //FlushFileBuffers(wfile);
     }
     end_time = timeGetTime();
     std::cout << end_time - start_time << std::endl;

     system("pause");
     return 0;
   }

更新 谢谢你的回答,这里是答案: 请参阅VS目录\ VS \ crt \ src \ fflush.c:

    //fflush.c
    int __cdecl _fflush_nolock (FILE *str) {
        //irrelevant codes
        if (str->_flag & _IOCOMMIT) {
                return (_commit(_fileno(str)) ? EOF : 0);
        }
        return 0;
    }

所以这里是一个_IOCOMMIT标志,然后看...... \ src \ fdopen.c

    FILE * __cdecl _tfdopen (int filedes, const _TSCHAR *mode) {
      //irrelevant codes
        while(*++mode && whileflag)
          switch(*mode) {
      //...
              case _T('c'):
                if (cnflag)
                    whileflag = 0;
                else {
                    cnflag = 1;
                    fileflag |= _IOCOMMIT;
                }
               break;
     //...
    }

_tfopen是由fopen在内部调用的,请参考fopen的文档,我发现这个:

” 模式: 'c'

启用关联文件名的提交标志,以便在调用fflush或_flushall时将文件缓冲区的内容直接写入磁盘。“ 因此,只有在调用fopen时设置了'c'标志时才会调用_commit。

_commit函数最终调用FlushFileBuffers。

除了这些,我发现当我只写一些数据到文件(不超过缓冲区大小)时,如果没有fflush的fwrite,文本将不会明显地被写入,而对于API,在WriteFile之后即使我没有' t调用FlushFileBuffers,当我打开文件(程序处于Sleep状态)时,内容自动写入文件,这也是我对flush的困惑之一,这个操作可能是由OS完成的,WriteFile将数据复制到系统缓存,它的文件缓冲区由操作系统管理,因此fflush()只在内部调用WriteFile而不进行实际刷新是合理的,系统知道何时刷新它们,可能在文件句柄关闭时或对此文件的另一个I / O访问时发生。 所以我修改了这个基准:

      start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
    fwrite(text, 1, size, cfile);
    fflush(cfile);
}
end_time = timeGetTime();
std::cout << end_time - start_time << '\n';

start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
    WriteFile(wfile, text, size, &written, NULL);
}
end_time = timeGetTime();
std::cout << end_time - start_time << std::endl;

结果是 次:99999 FWRITE:217 WriteFile的:171

因此,总之,加快API文件写入操作:

  1. 不要显式调用FlushFileBuffers,系统缓存中的数据会在需要时刷新到磁盘。

  2. 获取WriteFile的缓冲区,就像fwrite一样,因为API调用比简单的memcpy花费更多的时间,在填充缓冲区时调用WriteFile。

2 个答案:

答案 0 :(得分:15)

使用来自Sysinternals的Process Monitor (procmon)工具,您会看到对fflush()的调用与FlushFileBuffers(wfile)(或FILE_FLAG_WRITE_THROUGH标志的调用不同到CreateFile())。

fwrite()会将数据写入缓冲区,直到该缓冲区填满为止,这将导致它将缓冲区中的数据发送到WriteFile()调用。当您致电fflush()时,所有发生的事情都是当前缓冲区中的数据被传递给WriteFile()的来电 - fflush()不会调用FlushFileBuffers()

1:21:32.9391534 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 0, Length: 24
1:21:32.9392200 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 24, Length: 24
1:21:32.9392340 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 48, Length: 24
1:21:32.9392436 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 72, Length: 24
1:21:32.9392526 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 96, Length: 24
1:21:32.9392623 AM  test.exe    6132    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 120, Length: 24

为了进行比较,以下是fwrite()循环中没有fflush()调用的跟踪示例:

1:27:28.5675034 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 0, Length: 1,024
1:27:28.5676098 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 1,024, Length: 1,024
1:27:28.5676399 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 2,048, Length: 1,024
1:27:28.5676651 AM  test.exe    3140    WriteFile   C:\temp\file1.txt   SUCCESS Offset: 3,072, Length: 1,024

这是来自WriteFile()循环的跟踪片段(带有FILE_ATTRIBUTE_NORMAL标志和显式调用FlushFileBuffers() - 它只是使得在跟踪中发生的事情更容易看到,因为跟踪中会显示FlushFileBuffers()来电,而不是仅显示为第二次4KB WriteFile()来电。

1:21:29.0068503 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 24, Priority: Normal
1:21:29.0069197 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0069517 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0087574 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 24, Length: 24
1:21:29.0087798 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0088087 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0102260 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 48, Length: 24
1:21:29.0102428 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0102701 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal
1:21:29.0113444 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 72, Length: 24
1:21:29.0113602 AM  test.exe    6132    FlushBuffersFile    C:\temp\file2.txt   SUCCESS 
1:21:29.0113848 AM  test.exe    6132    WriteFile   C:\temp\file2.txt   SUCCESS Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal

因此,您的基准测试显示WriteFile()循环严重不利的原因仅仅是因为您有大约一千个FlushFileBuffers()的调用不在fwrite()循环中。< / p>

答案 1 :(得分:5)

如果设置正确,WriteFile() 可以fwrite()更有效率。 WriteFile()允许您在执行发出的IO请求时对其使用的条件进行精细定制。

例如,您可以绕过中间缓冲IO子系统并直接从您的数据指针中拉出,就像它是中间IO缓冲区一样,从而删除了一个重要的中间人。但是,这种设置有些限制。您的数据指针必须位于等于要写入的卷的扇区大小的字节边界上。由于显而易见的原因,fwrite()不存在此类工具。 Windows API爱好者(大约是里希特和他的弟兄们)非常喜欢使用WriteFile()这样的用法来挤压他们的Windows程序IO性能的最后一滴。

如果你想知道为什么人们不是WriteFile()爱孩子,我可以向你保证很多人,但 none 他们对便携式代码最不感兴趣。那些(或者只是 关注它的那些(Knuth所说的关于过早优化的内容......?),选择像fwrite()这样的标准设施。

如果您真的fwrite()的MSVCRT实现以及它如何执行它感兴趣,请查看源代码。它随每个版本的VC ++ Standard或更高版本一起提供(可能不是Express;我从未检查过)。