编译器是如何如此有效地优化getline()的

How does the compiler optimize getline() so effectively?

本文关键字:getline 优化 有效地 何如此 编译器      更新时间:2023-10-16

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

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

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

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

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

我有作为标记eof的唯一字符串的文本,所以它需要搜索每一行。我在手机上做这件事,所以我为格式问题道歉,但这很简单。我有一个函数,将我的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;

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

这成功地贯穿了每一行,并在408秒内返回终点位置。尝试将文件放在字符串流中,或者省略整个循环中的所有内容,我的改进微乎其微(直到最后都是getline,没有检查、搜索或显示)。此外,为字符串预先预留一个巨大的空间也无济于事。

似乎getline完全是驱动程序。然而如果我使用/O2标志(MSVC++)进行编译,我会得到快得离谱的26秒。此外,长线与短线之间没有明显的减速。显然,编译器正在做一些非常不同的事情。我没有抱怨,但对它是如何实现的有任何想法吗?作为一个练习,我想在编译器优化之前让我的代码执行得更快。

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

更新:我今晚回家后会发布代码。看起来只是关闭运行时检查就把执行时间从400秒降到了50秒!我尝试使用原始c风格的数组执行相同的功能。我不是很有经验,但将数据转储到一个字符数组中,并在其中循环查找换行符或目标字符串的第一个字母,这很容易。

即使在完全调试模式下,它也能在54秒内完成并正确找到字符串。取消检查时为26秒,优化后为20秒。所以,从我非正式的、特别的实验来看,字符串和流函数似乎受到了运行时检查的伤害?我回家后会再次检查。

这种显著加速的原因是iostream类层次结构基于模板(std::ostream实际上是一个名为std::basic_ostream的模板的typedef),并且它的许多代码都在头中。C++iostream需要几个函数调用来处理流中的每个字节。然而,这些函数中的大多数都相当琐碎。通过启用优化,这些调用中的大多数都是内联的,这向编译器暴露了这样一个事实,即std::getline本质上是将字符从一个缓冲区复制到另一个缓冲区时,直到它找到一条换行符——通常这是在几层函数调用下"隐藏"的。这可以进一步优化,将每个字节的开销减少几个数量级。

缓冲行为实际上在优化版本和非优化版本之间不会发生变化,否则速度会更高。