在Windows中fwrite比WriteFile快吗?

Fra*_*ahm 9 c++ performance file-io winapi fwrite

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

写入成本为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;
   }
Run Code Online (Sandbox Code Playgroud)

更新: 感谢您的回答,这里是答案:请参阅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;
    }
Run Code Online (Sandbox Code Playgroud)

所以这是一个_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;
     //...
    }
Run Code Online (Sandbox Code Playgroud)

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

"模式:'c'

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

_commit函数最终调用FlushFileBuffers.

除了这些之外,我发现当我只写一些数据到文件(不超过缓冲区大小)时,如果没有fflush的fwrite,文本显然不会被写,而对于API,在WriteFile后,即使我不调用FlushFileBuffers当我打开文件(程序处于休眠状态)时,内容会自动写入文件,这也是我对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;
Run Code Online (Sandbox Code Playgroud)

结果是次:99999 fwrite:217 WriteFile:171

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

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

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

Mic*_*urr 16

使用类似的工具进程监视器(procmon中) Sysinternals公司,你会看到呼叫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
Run Code Online (Sandbox Code Playgroud)

为了比较,这是一个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
Run Code Online (Sandbox Code Playgroud)

这里有一个来自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
Run Code Online (Sandbox Code Playgroud)

因此,您的基准测试显示出对WriteFile()循环的严重劣势的原因仅仅是因为您有大约一千个调用FlushFileBuffers()不在fwrite()循环中.


Who*_*aig 5

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

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

如果你想知道为什么人们不是WriteFile()爱孩子,我可以向你保证很多人,但他们中没有人对便携式代码最不感兴趣.是(或只是那些并不认为关注它(那是什么克努特说,大约过早优化..?),选择的标准设施喜欢fwrite().

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