sfb*_*bea 7 x86 bit-manipulation intrinsics compiler-optimization rust
这是在 Compiler Explorer 上进行实验的结果,以确定编译器(rustc)在涉及log2()
/leading_zeros()
和类似函数时的行为。我发现这个结果似乎既奇怪又令人担忧:
代码:
pub fn lzcnt0(val: u64) -> u64 {
val.leading_zeros() as u64
}
pub unsafe fn lzcnt1(val: u64) -> u64 {
core::arch::x86_64::_lzcnt_u64(val)
}
pub unsafe fn lzcnt2(val: u64) -> u64 {
asm_lzcnt(val)
}
#[inline]
pub unsafe fn asm_lzcnt(val: u64) -> u64 {
let lzcnt: u64;
core::arch::asm!("lzcnt {}, {}", in(reg) val, lateout(reg) lzcnt, options(nomem, nostack));
lzcnt
}
Run Code Online (Sandbox Code Playgroud)
输出:
example::lzcnt0:
test rdi, rdi
je .LBB0_2
bsr rax, rdi
xor rax, 63
ret
.LBB0_2:
mov eax, 64
ret
example::lzcnt1:
jmp core::core_arch::x86_64::abm::_lzcnt_u64
core::core_arch::x86_64::abm::_lzcnt_u64:
lzcnt rax, rdi
ret
example::lzcnt2:
lzcnt rdi, rax
ret
Run Code Online (Sandbox Code Playgroud)
编译器选项是为了最好地模拟货物的“发布”配置(使用 opt-level=3 进行良好的测量),否则尽最大努力让编译器优化功能。具体目标应该不重要,只要它针对x86-64,我已经尝试过了x86_64-{pc-windows-{msvc,gnu},unknown-linux-gnu}
。
所有这些输出应该与 相同lzcnt2
。指令性能表 lzcnt
显然是一条全面的快速指令,应该使用,并且在如此低级的函数中出现不必要的分支是令人沮丧的。更奇怪的是,函数在幕后_lzcnt_u64()
调用leading_zeros()
- 编译器很乐意将其神奇地消除(也没有检查或断言),但似乎不会为底层函数执行此操作。lzcnt
更重要的是,即使在这种情况下,编译器也不会内联指令?(实现#[inline]
也将函数标记为 a)当然,ajmp
并没有那么糟糕,但它完全没有必要,应该避免。
我在诸如log2
和(我推测)其他函数中看到类似的结果,这些函数ctlz
在其实现中依赖于 rust 编译器内在函数。
如果您充分了解编译器,任何澄清将不胜感激。我不喜欢无缘无故地编写大量实用函数,但如果没有更好的选择,我会这样做。
PS 如果您的答案是在大多数情况下性能增益可以忽略不计,和/或由于代码质量或类似推理我不应该关心:我理解这种情绪,但这不是这个问题的重点。我正在个人项目中编写裸机热代码。
归档时间: |
|
查看次数: |
207 次 |
最近记录: |