是Windows比WriteFile的fwrite的快?(Is fwrite faster than

2019-08-06 13:50发布

我一直认为,WriteFile的比FWRITE更有效,因为FWRITE再叫内部WriteFile的,但下面的测试代码显示我的fwrite快显著比WriteFile的。

fwrite的成本为2毫秒,而WriteFile的需要27000(FILE_ATTRIBUTE_NORMAL),都刷新每次写入调用之后。 如果我打电话的WriteFile与FILE_FLAG_WRITE_THROUGH,并发表意见FlushFileBuffers(wfile)线,WriteFile的会更快,它的成本800。

因此,它是真的FWRITE再叫WriteFile的? 是什么让如此巨大的差异? 如何fwrite的内部工作? 我怎么能写数据与API到文件比更有效地使用fwrite?(unbufferd,同步)。

   #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被调用相关的文件名提交标志。”所以,_commit只有当调用的fopen当‘C’标志设置调用。

该_commit函数最终调用FlushFileBuffers。

除了这些,我发现,当我只写了几个数据文件(不超过缓冲区大小),如果没有FWRITE fflush,文本将不会明显wrritten,而对于API,WriteFile的后即使我不叫FlushFileBuffers ,当我打开文件(程序处于休眠),内容被写入文件自动,这是为什么我很困惑大致齐平的原因之一,此操作可通过操作系统来实现,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的当缓冲区填满。

Answer 1:

使用类似的工具进程监视器(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

为了便于比较,这里是从一个图案的例子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()循环。



Answer 2:

如果正确设置, WriteFile() 可以比更有效fwrite() WriteFile()您可以精细调整它使用执行你发出IO请求时的条件。

例如,可以绕过中间缓冲IO子系统,并直接数据指针拉,好像它是在中间IO缓冲器,因此除去显著中间人。 设置,但是,有些限制。 您的数据指针必须躺在一个字节边界相当于卷的扇区大小被写入。 没有这样的设施与存在fwrite()的希望显而易见的原因。 Windows API的爱好者(约J.里克特和他的弟兄们)是具有这样的用法摆弄的大风扇WriteFile()到最后一滴排挤他们的Windows程序IO性能。

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

如果你在MSVCRT,实现真正感兴趣fwrite()以及它如何做它做什么,签出源。 它是随或者VC ++标准的每一个版本更大的(也许不能表达,我从来没有选中)。



文章来源: Is fwrite faster than WriteFile in windows?