【问题标题】:Rust target-cpu=native gets slower SIMD executionRust target-cpu=native 使 SIMD 执行速度变慢
【发布时间】:2021-07-30 07:18:11
【问题描述】:

我正在对 x86 内在函数的 Rust 包装器进行简单测试:莱布尼茨系列的 PI 近似值:

#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;

fn main() {
    let mut n: u64 = 0;
    let pi4 = std::f64::consts::PI / 4.0;
    unsafe {
        let mut dens = _mm256_set_pd(1.0f64, -3.0f64, 5.0f64, -7.0f64);
        let adder = _mm256_set_pd(8.0f64, -8.0f64, 8.0f64, -8.0f64);
        let ones = _mm256_set1_pd(1.0f64);
        let mut rsum = _mm256_set1_pd(0.0f64);
        let mut quotients: __m256d;
        loop {
            quotients = _mm256_div_pd(ones, dens);
            rsum = _mm256_add_pd(rsum, quotients);
            dens = _mm256_add_pd(dens, adder);
            n = n + 1;
            let vlow = _mm256_extractf128_pd(rsum, 0);
            let vhigh = _mm256_extractf128_pd(rsum, 1);
            let add_partial = _mm_add_pd(vlow, vhigh);
            let sum = _mm_cvtsd_f64(add_partial)
                + _mm_cvtsd_f64(_mm_unpackhi_pd(add_partial, add_partial));
            if f64::abs(pi4 - sum) < 1.0e-9 {
                break;
            }
        }
    }
    println!("Steps: {}", 4 * n);
}

在功能上,该程序按预期工作。我的 CPU 型号是“AMD A8-9600 RADEON R7”,并且:

$ rustc --target=x86_64-linux-kernel --print target-cpus
Available CPUs for this target:
    native         - Select the CPU of the current host (currently bdver4).

编译时:

$ cargo build --release

时间是:

$ time target/release/sotest 
real    0m1.668s
user    0m1.667s
sys 0m0.001s

但使用“本机”目标时运行速度较慢:

$ RUSTFLAGS="-C target-cpu=native" cargo build --release
...
$ time target/release/sotest
real    0m2.783s
user    0m2.778s
sys 0m0.004s

问题是“本机”目标 CPU 有什么问题?乍一看documentation,我期待一个利用我所有 CPU 提供的扩展的二进制文件:

编译器会将其转换为目标功能列表。

即使不考虑扩展,为什么会变慢?

顺便说一句,编译选择 avx 扩展会产生很大的提升:

RUSTFLAGS="-C target-feature=+avx" cargo build --release
...
real    0m0.358s
user    0m0.354s
sys 0m0.004s

编辑:使用 Ubuntu 20.04 内核 5.4.0-72-generic。 rustc 1.51.0

【问题讨论】:

  • 与减速分开,您通常希望在水平求和工作之前进行多次迭代。此外,Bulldozer 系列只有 128 位宽的 SIMD 执行单元,因此 256 位向量指令解码为 2 uop。使用两个单独的 128 位向量可以避免_mm256_extractf128_pd(vec, 1),基本上无需额外费用。它很便宜(任何端口或 BD 和 Zen1 的单 uop),但免费更好。 _mm_unpackhi_pd 也不是免费的,但使用 SIMD 进行除法可能是值得的。 (事实上​​,您可能会遇到除法延迟的瓶颈,这可能会隐藏 hsum 的工作。)
  • “事实上,你可能会遇到除法延迟的瓶颈,这可能会隐藏 hsum 的工作”......事实上,这就是正在发生的事情。测试时,我尝试每 16 次循环只添加一次,没有明显的时间变化:除法规则总时间。很遗憾知道推土机的“限制”。
  • 是的,考虑到延迟瓶颈,除非您可以有用地展开另一个包含 4 个元素的向量以并行运行两个 dep 链,否则可能没有任何收获。 2x vaddpd 是分开的,最长的 loop-carried dep 仅通过 vdivpd。但即使在 Excavator (bdver4) 上,FP 分频器也是部分流水线的。 8-22 周期延迟,但 8-16 周期吞吐量。 (agner.org/optimize/instruction_tables.pdf)。因此,可能还有一点空间可以使用另一个 128 位或 256 位分母向量展开,以最大限度地提高 FP 除法吞吐量。

标签: rust simd intrinsics avx


【解决方案1】:

我的猜测是您遇到了这个错误:https://github.com/rust-lang/rust/issues/83027https://github.com/rust-lang/rust/pullis/83084 已于 2021 年 3 月 17 日解决了这个问题。

错误是当使用native 时,target_feature 没有正确应用,这是所有内部函数都使用的。因此,您对内在函数的调用可能没有被内联。您应该查看个人资料以确认这一点。

更一般地说,我建议使用运行时 CPU 功能检测并正确使用 #[target_feature]。您应该只从至少启用了avx 功能的函数中调用对 32 字节向量进行操作的函数。

【讨论】:

  • 好的,谢谢。正如错误讨论中所建议的那样,明确: target-cpu=bdver4 用 1.51 stable 修复了我的问题。此外,在每晚 1.51 中,“本机”目标炒锅如预期。
猜你喜欢
  • 2015-12-27
  • 2019-07-27
  • 2016-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-23
  • 2018-07-19
  • 1970-01-01
相关资源
最近更新 更多