我一直认为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在内部如何工作?与fwrite相比,如何使用API​​更有效地将数据写入文件?(无缓冲,同步)。

   #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。

除此之外,我发现当我仅向文件中写入少量数据(不超过缓冲区大小)时,如果fwrite不带fflush,则显然不会写文本,而对于API,即使我不调用FlushFileBuffers,也要写在WriteFile之后,当我打开文件(程序处于 sleep 状态)时,内容会自动写入文件,这就是我对flush感到困惑的原因之一,此操作可能由OS完成,WriteFile将数据复制到系统缓存,并且它的文件缓冲区由OS管理,因此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
写入文件:171

因此,总而言之,要加快API文件的编写操作:
  • 不要显式调用FlushFileBuffers,系统高速缓存中的数据将在需要时刷新到磁盘。
  • 和fwrite一样,为WriteFile获取缓冲区,因为API调用花费的时间比简单的memcpy要多,请在缓冲区填满时调用WriteFile。
  • 最佳答案

    使用Sysinternals中的Process Monitor (procmon)之类的工具,您会看到对fflush()的调用与FlushFileBuffers(wfile)(或FILE_FLAG_WRITE_THROUGHCreateFile()标志)没有完成相同的操作。
    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()循环中。

    关于c++ - 在Windows中,fwrite比WriteFile快吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14290337/

    10-11 19:35