用C ++读取文件的奇特方法:奇怪的性能问题

时间:2010-07-22 17:15:53

标签: c++ performance file iterator

在C ++中读取文件的常用方法是:

std::ifstream file("file.txt", std::ios::binary | std::ios::ate);
std::vector<char> data(file.tellg());
file.seekg(0, std::ios::beg);
file.read(data.data(), data.size());

读取1.6 MB文件几乎是即时的。

但是最近,我发现std::istream_iterator并想尝试一下,以编码一个漂亮的单行方式来读取文件的内容。像这样:

std::vector<char> data(std::istream_iterator<char>(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator<char>());

代码很好,但非常慢。读取相同的1.6 MB文件大约需要2/3秒。我知道它可能不是阅读文件的最佳方式,但为什么如此慢?

以经典方式读取文件就像这样(我只谈论读取函数):

  • istream包含filebuf,其中包含文件
  • 中的数据块
  • read函数从filebuf中调用sgetn,它将字符从内部缓冲区逐个复制(无memcpy)到“data”的缓冲区
  • 当完全读取filebuf中的数据时,filebuf从文件中读取下一个块

使用istream_iterator读取文件时,它是这样的:

  • 向量调用* iterator获取下一个char(这只是读取一个变量),将其添加到最后并增加其自己的大小
  • 如果向量的已分配空间已满(不常发生),则执行重定位
  • 然后调用++ iterator从流中读取下一个char(运算符&gt;&gt;带有char参数,当然只调用filebuf的sbumpc函数)
  • 最后它将迭代器与结束迭代器进行比较,这是通过比较两个指针
  • 来完成的

我必须承认,第二种方式效率不高,但它比第一种方式慢至少200倍,这怎么可能?

我认为性能杀手是重定位或插入,但我尝试创建一个完整的向量并调用std :: copy,它的速度一样慢。

// also very slow:
std::vector<char> data2(1730608);
std::copy(std::istream_iterator<char>(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator<char>(), data2.begin());

3 个答案:

答案 0 :(得分:6)

你应该比较apple-to-apple。

您的第一个代码读取未格式化二进制数据,因为您使用函数成员“read”。而不是因为你顺便使用std :: ios_binary,请参阅http://stdcxx.apache.org/doc/stdlibug/30-4.html以获得更多解释,但简而言之:“二进制打开模式的效果经常被误解。它不会将插入器和提取器放入二进制文件中模式,因此抑制它们通常执行的格式化。二进制输入和输出仅由basic_istream&lt;&gt; :: read()和basic_ostream&lt;&gt; :: write()“

完成

因此,您使用istream_iterator的第二个代码读取格式化文本。它的速度慢了。

如果您想阅读未格式化的二进制数据,请使用istreambuf_iterator:

#include <fstream>
#include <vector>
#include <iterator>

std::ifstream file( "file.txt", std::ios::binary);
std::vector<char> buffer((std::istreambuf_iterator<char>(file)),
                          std::istreambuf_iterator<char>());   

在我的平台(VS2008)上,istream_iterator比read()慢约x100。 istreambuf_iterator表现更好,但仍然比read()慢x10。

答案 1 :(得分:3)

只有剖析才会告诉您原因。我的猜测是你所看到的只是与第二种方法相关的所有额外函数调用的开销。而不是一次调用来引入所有数据,而是在进行1.6M调用* ......或者其他类似的事情。

*其中许多是虚拟的,这意味着每次调用两个CPU周期。 (Tks Zan)

答案 2 :(得分:1)

迭代器方法一次读取一个字符的文件,而file.read在一次点击中读取它。

如果操作系统/文件处理程序知道您想要读取大量数据,那么可以进行大量优化 - 可以在磁盘主轴的一次旋转中读取整个文件,而不是从OS缓冲区复制数据到应用程序缓冲区。

当你进行逐字节传输时,操作系统不知道你真正想要做什么,所以不能执行这样的优化。