我一直认为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文件的编写操作:
最佳答案
使用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()
循环中。关于c++ - 在Windows中,fwrite比WriteFile快吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14290337/