我一直认为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文件写入操作:
不要显式调用FlushFileBuffers,系统缓存中的数据会在需要时刷新到磁盘。
获取WriteFile的缓冲区,就像fwrite一样,因为API调用比简单的memcpy花费更多的时间,在填充缓冲区时调用WriteFile。
答案 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;我从未检查过)。