编译器如何有效地优化 getline()?

How does the compiler optimize getline() so effectively?

我知道很多编译器的优化可能相当深奥,但我的示例非常简单,我想看看我是否能理解,是否有人知道它可以做什么。

我有一个 500 MB 的文本文件。我声明并初始化一个 fstream:

std::fstream file(path,std::ios::in)

我需要顺序读取文件。它是制表符分隔的,但字段长度未知,并且逐行变化。我需要对每一行进行的实际解析只增加了很少的时间(这真的让我感到惊讶,因为我在 getline 的每一行上都做了 string::find。我认为那会很慢)。

一般来说,我想在每一行中搜索一个字符串,并在找到它时中止循环。出于我自己的好奇心,我也让它递增并吐出行号,我确认这增加了很少的时间(5 秒左右),让我看看它如何超越短线并减慢长线。

我要找到的文本是标记 eof 的唯一字符串,因此它需要搜索每一行。我正在我的 phone 上执行此操作,因此对于格式问题我深表歉意,但这非常简单。我有一个函数,将我的 fstream 作为参考,将要查找的文本作为字符串并返回 std::size_t.

long long int lineNum = 0;
while (std::getline (file, line))
{
    pos = line.find(text);
    lineNum += 1;
    std::cout << std::to_string(lineNum) << std::endl;
    if (pos != -1) 
        return file.tellg():
 }
     return std::string::npos;

编辑:lingxi 指出这里不需要 to_string,谢谢。如前所述,完全省略行号计算和输出可以节省几秒钟,这在我预优化的示例中只占总数的一小部分。

这成功地跑遍了每一行,returns 408 秒到达结束位置。尝试将文件放入字符串流中,或者省略整个循环中的所有内容(只是 getline 直到最后,没有检查、搜索或显示),我的改进微乎其微。此外,为字符串预先保留一个巨大的 space 也没有帮助。

似乎getline完全是驱动程序。但是...如果我使用 /O2 标志 (MSVC++) 进行编译,我的速度会快得可笑 26 秒。此外,长线与短线相比没有明显的放缓。显然,编译器正在做一些非常不同的事情。我没有抱怨,但是关于它是如何实现的有什么想法吗?作为练习,我想尝试让我的代码在编译器优化之前执行得更快。

我打赌这与 getline 操作字符串的方式有关。只为字符串保留整个文件大小,并逐个字符读取,在我传递 /n 时递增我的行号会更快吗(可惜暂时无法测试)?另外,编译器会使用 mmap 之类的东西吗?

更新:今晚回家后我会post编码。看起来只要关闭运行时检查就可以将执行时间从 400 秒减少到 50 秒!我尝试使用原始 C 样式数组执行相同的功能。我不是很有经验,但很容易将数据转储到字符数组中,然后循环查找换行符或目标字符串的第一个字母。

即使在完全调试模式下,它也会在 54 秒内完成并正确找到字符串。 26 秒关闭检查,20 秒优化。因此,从我非正式的临时实验来看,字符串和流函数似乎受到了运行时检查的影响?再一次,我回家后会仔细检查。

这种显着加速的原因是 iostream class 层次结构是基于模板的(std::ostream 实际上是一个名为 std::basic_ostream 的模板的类型定义),并且很多它的代码在 headers 中。 C++ iostream 调用多个函数来处理流中的每个字节。然而,这些功能中的大多数都是相当微不足道的。通过启用优化,这些调用中的大多数都是内联的,向编译器暴露了这样一个事实,即 std::getline 本质上是将字符从一个缓冲区复制到另一个缓冲区,直到它找到换行符 - 通常这是 "hidden" 在几层函数调用。这可以进一步优化,将每字节的开销减少几个数量级。

优化版和non-optimized版本之间的缓冲行为实际上没有改变,否则加速会更高。