做了什么:
这是在编译器资源管理器上进行实验的结果,以确定编译器(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
}
输出:
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
编译器选项是为了最好地模拟 cargo 的"发布"配置(opt-level=3 以获得良好的度量),否则我会尽力让编译器优化函数。具体目标应该无关紧要,只要它针对x86-64,我就试过x86_64-{pc-windows-{msvc,gnu},unknown-linux-gnu}
.
预期结果:
所有这些输出都应与lzcnt2
相同。指令性能表lzcnt
显然是一个全面的快速指令,应该使用,在如此低级的函数中有一个不必要的分支是令人沮丧的。更奇怪的是,_lzcnt_u64()
调用的函数leading_zeros()
幕下 - 编译器很乐意将其魔术消除(也没有检查或断言),但似乎不会为底层函数执行此操作。更重要的是,即使在这种情况下,编译器也不会内联lzcnt
指令?(实现也将函数标记为#[inline]
)当然,jmp
并没有那么糟糕,但它完全没有必要,应该避免。
它可能是什么:
- 编译器错误?
- 我不明白有目的的选择?
- 我不明白如何正确使用编译器资源管理器?
- 其他?
我在像log2
这样的函数中看到类似的结果,以及(我认为)其他依赖于其实现中固有的ctlz
rust 编译器的函数。
如果您充分了解编译器,任何澄清将不胜感激。我不喜欢无缘无故地编写大量实用程序函数,但是如果没有更好的选择,我会这样做。
附言如果你的答案是在大多数情况下性能提升可以忽略不计,和/或由于代码质量或类似的推理,我不应该关心:我理解这种情绪,但这不是这个问题的重点。我正在为个人项目中的裸机热代码编写。
旧的x86-64 CPU不支持lzcnt
,所以rustc/llvm默认不会发出它。 (他们会按bsr
执行它,但行为并不相同。
使用-C target-feature=+lzcnt
启用它。 尝试。
更一般地说,您可能希望使用-C target-cpu=XXX
来启用特定 CPU 型号的所有功能。 将rustc --print target-cpus
用于列表。
特别是,-C target-cpu=native
将为运行 rustc 本身的 CPU 生成代码,例如,如果您将在编译它的同一台机器上运行代码。