【问题标题】:Why is ifstream::read much faster than using iterators?为什么 ifstream::read 比使用迭代器快得多?
【发布时间】:2015-06-27 19:36:08
【问题描述】:

事实上,有很多方法可以将文件读入字符串。 两个常见的方法是使用 ifstream::read 直接读取字符串并使用 steambuf_iterators 和 std::copy_n:

使用 ifstream::read:

std::ifstream in {"./filename.txt"};
std::string contents;
in.seekg(0, in.end);
contents.resize(in.tellg());
in.seekg(0, in.beg);
in.read(&contents[0], contents.size());

使用 std::copy_n:

std::ifstream in {"./filename.txt"};
std::string contents;
in.seekg(0, in.end);
contents.resize(in.tellg());
in.seekg(0, in.beg);
std::copy_n(std::streambuf_iterator<char>(in), 
            contents.size(), 
            contents.begin();

许多基准测试表明,第一种方法比第二种方法快得多(在我使用 g++-4.9 的机器上,使用 -O2 和 -O3 标志的速度大约快 10 倍),我想知道这可能是什么原因这种性能差异。

【问题讨论】:

  • 我怀疑迭代器一次读取一个字符。这可以解释差异。
  • @RSahu:更具体地说,迭代器对每个字节使用 1+ 次虚拟调用,而 read 是每个缓冲区 1-2 次虚拟调用。
  • 旁注:你也可以像std::string contents(std::streambuf_iterator&lt;char&gt;(in), {});这样就地构造字符串,不需要copy_n和获取文件的大小。但在速度方面可能不会有很大的不同。
  • @RSahu 确实不应该,那将是一个糟糕的实现。事实上,我想我记得 libstdc++ 实现对文件缓冲区迭代器非常聪明,甚至预先确定了缓冲区的大小(这需要确定缓冲区迭代器指向一个可查找的文件)。不过我可能记错了。
  • 我们所说的文件有多大?这可能取决于此。此外,您可能需要小心地将整个内容以一大块的形式读入内存,而不知道它可能有多大。根据您正在执行的处理类型,该程序一次读取一行可能会更有效。

标签: c++ performance optimization io iterator


【解决方案1】:

read 是单个 iostream 设置(每个 iostream 操作的一部分)和对操作系统的单个调用,直接读取您提供的缓冲区。

迭代器通过重复提取单个charoperator&gt;&gt; 来工作。由于缓冲区大小,这可能意味着更多的操作系统调用,但更重要的是,这也意味着重复设置和拆除 iostream 哨兵,这可能意味着互斥锁,通常意味着一堆其他东西。此外,operator&gt;&gt; 是格式化的操作,而 read 是未格式化的,这是每个操作的额外设置开销。

编辑:疲倦的眼睛看到了 istream_iterator 而不是 istreambuf_iterator。当然 istreambuf_iterator 不做格式化输入。它在streambuf上调用sbumpc或类似的东西。还是有很多调用,而且使用了缓冲区,可能比整个文件还小。

【讨论】:

  • “迭代器通过使用operator&gt;&gt; 重复提取单个字符来工作”——有什么参考吗?事实上,operator&gt;&gt; 进行格式化输入,而 istreambuf_iterator 进行 unformatted 输入,所以我认为这不太可能。 (编辑:根据cppreference.com 这个答案是错误的。)
  • 这有点像 streambuf_iterator 的定义,Konrad。每次使用时,都会从对应的流缓冲区中提取一个字符。
  • @Peter 重点主要是关于格式化和未格式化的输入,这有很大的不同。下一个问题是编译器是否可以内联迭代器operator*的调用链中的所有调用,然后执行循环展开。或者 std::copy{_n} 的实现将调用分派给一个方法是否合法,该方法用更有效的方法手动替换迭代器复制(类似于std::copyIterT=char* 所做的事情,它被分派到std::memcpy现代实现)。
  • edit:该标准确实要求 basic_istream::read 创建一个哨兵对象,但没有明确要求它必须调用 sbumpc/sgetc 来获取字符。因此,单个操作系统调用似乎是合理的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-10
  • 1970-01-01
  • 1970-01-01
  • 2018-11-17
  • 2015-10-20
相关资源
最近更新 更多