为什么std :: count和std :: find优化使用memchr?

时间:2017-04-18 22:41:35

标签: c++ performance c++-standard-library

我正在阅读sehe's answerthis question并且惊讶地看到使用std::memchr的手写循环发现比使用快3倍 std::count(见评论)。使用std::count的代码可以在编辑2中看到,但它基本上归结为:

const auto num_lines = std::count(f, l, '\n');

vs

uintmax_t num_lines = 0;
while (f && f != l)
    if ((f = static_cast<const char*>(memchr(f, '\n', l - f))))
        num_lines++, f++;

我原本期望std::count版本至少与std::memchr版本一样快 - 出于类似的原因using std::copy should be at least as fast as std::memcpy

我检查了std::count的标准库(libc ++)实现,并且没有尝试优化char输入类型(同上std::find)。

这是为什么?如果提供std::memchr迭代器和char*值,实现是否可以调度到char

1 个答案:

答案 0 :(得分:3)

如果匹配之间的平均距离不小,则使用memchr的实际函数调用只是一个胜利。

特别是对于count,如果您计算memchr个字符时平均每2个或每4个字符调用t,则调用memchr可能会慢很多。(例如,使用DNA库 - 使用ACGT字母表对。

我对使用std::count循环作为char超过find数组的默认实现持怀疑态度。有更多可能的其他方法来调整源代码,以便编译为更好的asm。

对于std::count,它会更有意义,即使它在前一个字节中显着增加了开销,如果在前几个字节中命中了一个简单的字节。< / p>

您还可以将此视为编译器遗漏优化。如果编译器为std::findstd::count中的循环编写了更好的代码,那么调用手写的asm库函数可以获得更少的代码。

当进入循环之前未知行程计数时,gcc和clang从不自动向量化循环。 (即他们不进行搜索循环,这是对于像字节一样小的元素大小的主要错过优化)。 ICC没有这个限制,可以矢量化搜索循环。我不知道如何使用libc ++的std :: count或find。

-O3必须检查每个元素,因此它应该自动向量化。但是,如果gcc或clang甚至没有pcmpeqb,那么那是不幸的。它应该在x86上用paddb(打包比较字节)很好地矢量化,然后psadbw 0 / -1比较结果。 (至少每255次迭代,jmp对零,以水平求和字节元素。)

库函数调用开销至少是一个带内存函数指针的间接调用(可以缓存未命中)。在具有动态链接的Linux上,通过PLT通常还需要-fno-plt(除非您使用memchr进行编译)。 strchr更容易优化,启动开销低于strchr,因为您可以快速检查16B向量加载是否可以超过结束(而不是对齐strlen或{{的指针1}}以避免跨越页面或缓存行边界)

如果调用memchr是在asm中实现某些东西的最佳方法,那么从理论上讲,编译器应该发出什么。 gcc / clang已经优化了大型复制循环以调用libc memcpy,具体取决于目标选项(-march=)。例如当副本足够大以至于lib​​c版本可能决定在x86上使用NT存储。