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

Rust compiler not optimising lzcnt? (and similar functions)

完成了什么:

这是在 Compiler Explorer 上进行实验的结果,以确定编译器 (rustc) 在涉及 log2()/leading_zeros() 和类似函数时的行为。我发现这个结果似乎既奇怪又令人担忧:

Compiler Explorer link

代码:

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 的 'release' 配置(使用 opt-level=3 作为一个很好的衡量标准),否则尽我最大的努力让编译器优化功能。具体目标应该无关紧要,只要它针对 x86-64,我已经尝试过 x86_64-{pc-windows-{msvc,gnu},unknown-linux-gnu}.

预期结果:

所有这些输出都应与 lzcnt2 相同。 Instruction Performance Tables lzcnt 显然是一个全面的快速指令,应该使用,在如此低级的功能中有一个不必要的分支是令人沮丧的。更奇怪的是,函数 _lzcnt_u64() 在幕后调用 leading_zeros() - 编译器很乐意将其魔术化(也没有检查或断言),但似乎不会为底层函数执行此操作。更何况,即使在那种情况下,编译器也不会内联 lzcnt 指令? (实现也标记了函数 a #[inline])当然,a jmp 没有那么糟糕,但完全没有必要,应该避免。

它可能是什么:

我在 log2 和(我想)其他依赖 ctlz rust 编译器实现的函数中看到了类似的结果。

如果您充分了解编译器,将不胜感激。出于某种原因,我不喜欢编写大量实用函数,但如果没有更好的选择,我会这样做。

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

旧的 x86-64 CPUs 不支持 lzcnt,所以 rustc/llvm 默认不会发出它。 (他们会以 bsr 的形式执行它,但行为并不相同。)

使用-C target-feature=+lzcnt启用它。 Try.

更一般地说,您可能希望使用 -C target-cpu=XXX 来启用特定 CPU 模型的所有功能。使用 rustc --print target-cpus 作为列表。

特别是,-C target-cpu=native 将为 rustc 本身 运行 正在运行的 CPU 生成代码,例如如果您将 运行 代码放在您正在编译它的同一台机器上。