我有这个memchr
代码,我正在尝试使它进行非分支:
.globl memchr
memchr:
mov %rdx, %rcx
mov %sil, %al
cld
repne scasb
lea -1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
我不确定cmove
是否是分支指令。是吗?如果是这样,如何重新排列我的代码,使其不会分支?
不,它不是一个分支,这就是cmovcc
的全部意义。
它是对两个输入都具有数据依赖关系的 ALU 选择,而不是控件依赖关系。 (使用内存源时,它会无条件加载内存源,这与真正 NOP 的 ARM 谓词加载指令不同。 因此,您不能将其与可能不好的指针一起使用,以进行无分支边界或 NULL 检查。 这也许是最清楚的例证,它绝对不是一个分支。
但无论如何,它没有以任何方式预测或推测;就CPU调度程序而言,它就像一个adc
指令:2个整数输入+ FLAGS和1个整数输出。 (与adc
/sbb
的唯一区别是它不写标志。 当然,并且在具有不同内部结构的执行单元上运行(。
这是好是坏完全取决于用例。 另请参阅 gcc 优化标志 -O3 使代码比 -O2 慢,了解有关cmov
上行/下行的更多信息
请注意,repne scasb
并不快。快速字符串"仅适用于代表 stos/movs。
repne scasb
在现代 CPU 上每个时钟周期运行约 1 个计数,即通常比简单的 SSE2pcmpeqb
/pmovmskb
/test+jnz
循环差约 16 倍。 通过巧妙的优化,您可以更快,每个时钟最多 2 个矢量使负载端口饱和。
(例如,参见glibc的memchr
,用于将整个缓存行的ORing结果pcmpeqb
一起馈送一个pmovmskb
,IIRC。 然后回去整理一下实际命中的位置。
repne scasb
也有启动开销,但微码分支与常规分支不同:它不是在英特尔 CPU 上预测的分支。 因此,这不会错误预测,但对于除了非常小的缓冲区之外的任何性能来说,这完全是垃圾。
SSE2 是 x86-64 的基线,高效的未对齐负载 +pmovmskb
使其成为memchr
的明智之选,您可以在其中检查长度>= 16 以避免进入未映射的页面。
快速 strlen:
- 为什么启用优化后此代码慢 6.5 倍?显示了使用 SSE2 的 16 字节对齐输入的简单未展开的 strlen。
- 为什么glibc的strlen需要如此复杂才能快速运行?链接到glibc中有关手动优化的ASM strlen函数的更多内容。 (以及如何在 GNU C 中使 bithack strlen 避免严格混叠 UB。
- https://codereview.stackexchange.com/a/213558 标量Bithack strlen,包括与glibc问题相同的一次4字节bithack。 比一次字节更好,但使用 SSE2 毫无意义(x86-64 保证(。 但是,@CodyGray的教程式答案可能对初学者有用。 请注意,它没有考虑在 x86 和 x64 上读取同一页面中缓冲区末尾是否安全?