【问题标题】:Fancy way to read a file in C++ : strange performance issue用 C++ 读取文件的好方法:奇怪的性能问题
【发布时间】:2011-03-19 16:45:04
【问题描述】:

在 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)到“数据”的缓冲区
  • 当filebuf里面的数据全部读完后,filebuf从文件中读取下一个块

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

  • 向量调用 *iterator 来获取下一个字符(这只是读取一个变量),将其添加到末尾并增加自己的大小
  • 如果向量的分配空间已满(这种情况并不经常发生),则会执行重定位
  • 然后它调用 ++iterator 从流中读取下一个字符(操作符 >> 带有 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());

【问题讨论】:

  • “我认为性能杀手是重定位或插入” - 这就是您需要依赖分析的原因。

标签: c++ performance file iterator


【解决方案1】:

你应该比较苹果与苹果。

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

因此,您的第二个带有 istream_iterator 的代码会读取 formatted 文本。速度很慢。

如果要读取未格式化的二进制数据,请使用 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() 慢 10 倍。

【讨论】:

  • 谢谢,我没有考虑语言环境、提取宽度等。
  • 但是内存中数据的处理怎么会比文件I/O慢100倍呢?
【解决方案2】:

只有分析才能告诉您确切的原因。我的猜测是,您所看到的只是与第二种方法相关的所有额外函数调用的开销。您正在执行 160 万次调用*...或类似的操作,而不是一次调用来引入所有数据。

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

【讨论】:

  • 是的,这些调用是间接虚拟的。那些糟透了。
  • Profiling 告诉我,很多函数消耗了每个函数的 3% 到 5%,没有函数出现在顶部;我将您标记为已接受的答案
【解决方案3】:

迭代器方法一次读取一个字符,而 file.read 一次读取。

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

当您进行逐字节传输时,操作系统不知道您真正想要做什么,因此无法执行此类优化。

【讨论】:

  • fstream内部的filebuf对象逐块读取文件。无论如何,在这两种情况下读取文件的方式都是一样的,只是从 filebuf 复制到 vector 很慢
猜你喜欢
  • 2011-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多