C和C++风格文件IO的性能差异

29

我一直听说C++文件I/O操作比C风格的I/O操作慢得多得多。但是我没有找到任何关于它们实际有多慢的实用参考资料,所以我决定在我的机器上进行测试(Ubuntu 12.04,GCC 4.6.3,ext4分区格式)。

首先我在硬盘上写了一个大约900MB的文件。

C++ (ofstream):163秒

ofstream file("test.txt");
    
for(register int i = 0; i < 100000000; i++) 
    file << i << endl;

C (fprintf函数): 12秒

FILE *fp = fopen("test.txt", "w");
    
for(register int i = 0; i < 100000000; i++) 
    fprintf(fp, "%d\n", i);

我本来就期望出现这样的输出结果,它表明在C++中写文件要比C慢得多。然后我使用C和C++ I/O读取同一个文件。让我惊喜的是,在从文件中读取时几乎没有性能差异。

C++ (ifstream): 12秒

int n;
ifstream file("test.txt");

for(register int i = 0; i < 100000000; i++) 
    file >> n;

C (fscanf): 12个字符

FILE *fp = fopen("test.txt", "r");
    
for(register int i = 0; i < 100000000; i++) 
    fscanf(fp, "%d", &n);

那么,为什么使用流进行写操作需要这么长时间?或者为什么使用流进行读操作比写操作快得多?

结论:罪魁祸首是std::endl,正如答案和评论所指出的那样。将行 file << i << endl; 改为 file << i << '\n'; 将运行时间从163秒减少到16秒。


2
你关闭了优化吗?也许在 ifstream 的情况下,编译器看到你不断地覆盖 n,只是移动文件指针而没有实际从文件中读取数据? - Alex1985
4
叹息。没有区别,你的基准测试有缺陷。如果有什么区别,C++格式化输入/输出更快。这不是已经有无数个重复的了吗? - Konrad Rudolph
我不否认fstreamcstdio慢,但这些差异似乎比我预期的要大一些。你运行时没有低(或无)优化级别吧?由于流主要是通过模板实现的,因此它会被编译到您的代码中,而cstdio类型函数则编译为库并具有更高级别的优化。 - Mats Petersson
13
你在作弊。std::endl 比你在 fprintf 中输出的 \n 更多,它会导致流刷新。所以尝试使用 file << i << '\n' - Arne Mertz
1
可能是为什么C++输出比C慢得多?的重复问题。 - Christian Rau
显示剩余2条评论
3个回答

31

你正在使用 endl 来打印换行符。这就是问题所在,因为它不仅打印换行符,还刷新缓冲区,而这是一个昂贵的操作(如果在每次迭代中执行)。

如果你的意思只是要打印换行符,应该使用 \n

file << i << '\n';

并且,必须在发布模式下编译您的代码(即打开优化)。


2
谢谢,那就是问题所在。改为 '\n' 后,时间缩短到了16秒! - Rafi Kamal
3
C和C++风格文件IO之间的性能差异实际上并不存在吗? - Alexandru Barbarosie
2
@AlexandruBarbarosie:是的,没有理由不这样做。 - Nawaz
1
嗯,16秒对比12秒,25%的差异还是相当可观的吧? - rustyx
没问题,**在这里**。我在MSVC2015U3,Windows 10 x64上尝试了一下,性能相差30%。但是!在FreeBSD 11(clang 3.4.1)上运行相同的代码,在两种情况下都显示完全相同的结果。所以我认为它是与实现相关的。 - rustyx
显示剩余6条评论

24
不,C++的输入/输出速度与C相比并没有显著变慢-如果有什么区别的话,现代实现应该在格式化输入/输出方面稍微快一点,因为它不需要解析格式字符串,而是通过链接流运算符在编译时确定格式化。
以下是一些考虑在基准测试中要注意的细节:
  • 使用完整优化(-O3)进行编译以进行公正比较。
  • 适当的基准测试需要估计偏差-实际上,这意味着您需要重复测试并交错它们。目前,您的代码无法抵御来自后台进程的干扰。您还应该报告重复运行的摘要统计数据,以捕获扭曲估计值的异常值。
  • 使用std::ios_base::sync_with_stdio(false);禁用C++流与C流的同步
  • 使用'\n'而不是(刷新)std::endl
  • 不要使用register声明-这根本没有任何区别,现代编译器可能会忽略它。

非常好的观点。感谢您对原始评论进行详细阐述。 - Ross Youngblood

3
使用 fstream 处理大文件时,请确保设置流缓冲区 > 0
反直觉的是,禁用流缓冲会极大地降低性能。至少在 MSVC 2015 实现中,在没有设置缓冲区时(参见 streambuf::xsputn),每次只复制 1 个字符filebuf 中,这可能使您的应用程序受 CPU 限制,从而导致较低的 I/O 速率。
const size_t bufsize = 256*1024;
char buf[bufsize];
mystream.rdbuf()->pubsetbuf(buf, bufsize);

您可以在这里找到一个完整的示例应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接