【发布时间】:2012-12-26 18:05:27
【问题描述】:
我一直认为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 更高效地将数据写入文件?(无缓冲,同步)。
#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,即使我没有写入文件,也可以在 WriteFile 之后' t调用FlushFileBuffers,当我打开文件(程序处于睡眠状态)时,内容会自动写入文件,这就是我对flush感到困惑的原因之一,这个操作可能是由操作系统完成的,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 写入:217 写文件:171
所以,总而言之,要加快 API 文件写入操作:
不要显式调用FlushFileBuffers,系统缓存中的数据会在需要时刷新到磁盘。
为 WriteFile 获取缓冲区,就像 fwrite 一样,因为 API 调用比简单的 memcpy 花费更多时间,当缓冲区填满时调用 WriteFile。
【问题讨论】:
-
效率是什么意思?应用程序响应速度更快,或者 API 调用和数据在介质上的时间尽可能有序?
-
您是否尝试过指定
GENERIC_ALL? -
@lego 我的意思是写调用的速度
-
@Mehrdad GENERIC_ALL 在这里无效,我试过 GENERIC_READ|GENERIC_WRITE,800+ 毫秒
-
尝试将共享模式
FILE_SHARE_READ更改为 0。这将独占打开文件,这可能会产生影响。
标签: c++ performance winapi file-io fwrite