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文件写入操作:
不要显式调用FlushFileBuffers,系统缓存中的数据将在需要时刷新到磁盘.
获取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()
循环中.
如果设置正确,WriteFile()
可以比效率更高fwrite()
.WriteFile()
允许您在执行发出的IO请求时精细定制其使用的条件.
例如,可以绕过中间缓冲IO子系统,并直接从拉你的数据指针,好像它是在中间IO缓冲器,因此除去显著中间人.但是,这种设置有些限制.您的数据指针必须位于等于要写入的卷的扇区大小的字节边界上.fwrite()
出于希望明显的原因,不存在这样的设施.Windows API爱好者(大约是里希特和他的弟兄们)非常喜欢使用这些用法WriteFile()
来挤压他们的Windows程序IO性能的最后一滴.
如果你想知道为什么人们不是WriteFile()
爱孩子,我可以向你保证很多人,但他们中没有人对便携式代码最不感兴趣.是(或只是那些并不认为关注它(那是什么克努特说,大约过早优化..?),选择的标准设施喜欢fwrite()
.
如果您真的对MSVCRT实现fwrite()
及其执行方式感兴趣,请查看源代码.它随VC++ Standard或更高版本的每个版本一起提供(可能不是Express;我从未检查过).