为什么 std::count 和 std::find 没有针对使用 memchr 进行优化?
Why aren't std::count and std::find optimised to use memchr?
我正在阅读 sehe 对这个问题的回答,并惊讶地发现使用std::memchr
的手写循环比使用std::count
快3 倍以上(见评论)。使用std::count
的代码可以在 edit 2 中看到,但它基本上可以归结为:
const auto num_lines = std::count(f, l, 'n');
与
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 出现一次时,调用memchr
可能会慢得多。 (例如,使用ACGT字母表的DNA碱基对)。
我对使用memchr
循环作为std::count
char
数组的默认实现持怀疑态度。 更有可能有其他方法可以调整源代码,以便它编译为更好的asm。
对于find
来说,这将更有意义,即使它确实可能会显着增加开销,而不是在前几个字节中有命中时,一次简单的字节循环。
您也可以将其视为编译器错过的优化。 如果编译器为std::count
和std::find
中的循环制作了更好的代码,那么调用手写的asm库函数的收益就会减少。
GCC 和 Clang 在进入循环之前不知道行程计数时,从不自动矢量化循环。 (即它们不执行搜索循环,这对于小至字节的元素大小是一个主要的遗漏优化)。 ICC 没有此限制,并且可以矢量化搜索循环。 不过,我还没有看过libc ++的std::count或find是如何做到的。
std::count
必须检查每个元素,因此它应该自动矢量化。 但是,如果 gcc 或 clang 甚至没有-O3
,那就很不幸了。 它应该在 x86 上使用pcmpeqb
(打包的比较字节)很好地矢量化,然后paddb
0/-1 比较结果。 (至少每 255 次迭代一次,psadbw
与零相对以水平对字节元素求和)。
库函数调用开销至少是使用来自内存的函数指针的间接调用(可能会缓存未命中)。 在具有动态链接的 Linux 上,通常也会通过 PLT 进行额外的jmp
(除非您使用-fno-plt
进行编译)。memchr
比strchr
更容易优化,启动开销低,因为您可以快速检查 16B 矢量加载是否可以超过末尾(而不是对齐指针strchr
或strlen
以避免越过页面或缓存行边界)
如果调用memchr
是在asm中实现某些东西的最佳方式,那么理论上这就是编译器应该发出的。 GCC/CLANG 已经优化了对 libcmemcpy
调用的大型复制循环,具体取决于目标选项(-march=
)。 例如,当副本足够大时,libc 版本可能会决定在 x86 上使用 NT 存储。
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 使用一个考虑到std::map中键值的滚动或换行的键
- 如何从 std::atomic 中提取指针 T<T>?
- 为什么 std::unique 不调用 std::sort?
- 使用std::函数映射对象方法
- 为什么 std::count 和 std::find 没有针对使用 memchr 进行优化?