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

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 如果您的答案是在大多数情况下性能增益可以忽略不计,和/或由于代码质量或类似推理我不应该关心:我理解这种情绪,但这不是这个问题的重点。我正在个人项目中编写裸机热代码。

Nat*_*dge 8

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