为什么不std :: count和std :: find优化使用memchr?
我正在阅读sehe对这个问题的回答,很惊讶地看到使用std::memchr
手写循环比使用std::count
(见注释) 快了3倍 。 使用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
版本一样快 - 这也是为什么std::copy
应该至少和std::memcpy
一样快的原因。
我检查了我的标准库(libc ++) std::count
,并且没有试图针对char
输入类型进行优化(对于std::find
同上)。
为什么是这样? 如果提供了char*
迭代器和char
值,实现是否可以不派遣到std::memchr
?
如果匹配之间的平均距离不小,那么对memchr
使用实际函数调用只会是一场胜利。
特别是对于count
,如果您计算t
字符时平均每2或4(例如,使用ACGT字母表的DNA碱基对),则调用memchr
可能会慢很多。
我会怀疑使用memchr
循环作为char
数组的std::count
的默认实现。 更有可能的其他方式来调整来源,使其编译更好。
对于find
它会更有意义,即使它可能会显着增加开销,而对于一个简单的一次一个字节的循环,如果在第一对字节中有一个命中的话。
您也可以将其视为编译器错失优化。 如果编译器为std::count
和std::find
的循环创建了更好的代码,那么通过调用手写的asm库函数就可以获得更少的代码。
gcc和clang在进入循环之前并不知道行程计数时不会自动矢量化循环。 (即它们不会执行搜索循环,这对于像字节一样小的元素大小而言是主要的遗漏优化)。 ICC没有这种限制,可以矢量化搜索循环。 尽管如此,我还没有看过它是如何处理libc ++的std :: count或find的。
std::count
必须检查每个元素,所以它应该自动向量化。 但如果海湾合作委员会或铿锵甚至不与-O3
,那么这是不幸的。 它应该在x86上使用pcmpeqb
(打包比较字节)很好地进行矢量化,然后paddb
为0 / -1比较结果。 (至少每255次迭代, psadbw
对零来水平地求和字节元素)。
库函数调用开销至少是一个从内存中调用函数指针的间接调用(可以缓存未命中)。 在具有动态链接的Linux上,通常还有一个额外的jmp
通过PLT(除非使用-fno-plt
编译)。 memchr
比strchr
更容易优化,启动开销strchr
,因为您可以快速检查16B矢量负载是否可以越过末端(对比指向strchr
或strlen
的指针以避免跨越页面或缓存行边界)
如果调用memchr
是用asm实现某些东西的最佳方式,那么理论上这就是编译器应该发出的东西。 gcc / clang已经根据目标选项( -march=
)优化了大型复制循环以调用libc memcpy
。 例如,当副本足够大,libc版本可能决定在x86上使用NT存储时。
上一篇: Why aren't std::count and std::find optimised to use memchr?