Rust 编译器没有优化 lzcnt?(和类似功能)



做了什么:

这是在编译器资源管理器上进行实验的结果,以确定编译器(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这样的函数中看到类似的结果,以及(我认为)其他依赖于其实现中固有的ctlzrust 编译器的函数。

如果您充分了解编译器,任何澄清将不胜感激。我不喜欢无缘无故地编写大量实用程序函数,但是如果没有更好的选择,我会这样做。

附言如果你的答案是在大多数情况下性能提升可以忽略不计,和/或由于代码质量或类似的推理,我不应该关心:我理解这种情绪,但这不是这个问题的重点。我正在为个人项目中的裸机热代码编写。

旧的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 生成代码,例如,如果您将在编译它的同一台机器上运行代码。

最新更新