我正在阅读sehe's answer到this 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
?
答案 0 :(得分:3)
如果匹配之间的平均距离不小,则使用memchr
的实际函数调用只是一个胜利。
特别是对于count
,如果您计算memchr
个字符时平均每2个或每4个字符调用t
,则调用memchr
可能会慢很多。(例如,使用DNA库 - 使用ACGT字母表对。
我对使用std::count
循环作为char
超过find
数组的默认实现持怀疑态度。有更多可能的其他方法来调整源代码,以便编译为更好的asm。
对于std::count
,它会更有意义,即使它在前一个字节中显着增加了开销,如果在前几个字节中命中了一个简单的字节。< / p>
您还可以将此视为编译器遗漏优化。如果编译器为std::find
和std::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=
)。例如当副本足够大以至于libc版本可能决定在x86上使用NT存储。