SIMD代码在Debug中工作,但不在Release中

kal*_*ali 9 simd rust

此代码在调试模式下工作,但由于在释放模式下断言而导致混乱.

use std::arch::x86_64::*;

fn main() {
    unsafe {
        let a = vec![2.0f32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
        let b = -1.0f32;

        let ar = _mm256_loadu_ps(a.as_ptr());
        println!("ar: {:?}", ar);

        let br = _mm256_set1_ps(b);
        println!("br: {:?}", br);

        let mut abr = _mm256_setzero_ps();
        println!("abr: {:?}", abr);

        abr = _mm256_fmadd_ps(ar, br, abr);
        println!("abr: {:?}", abr);

        let mut ab = [0.0; 8];
        _mm256_storeu_ps(ab.as_mut_ptr(), abr);
        println!("ab: {:?}", ab);

        assert_eq!(ab[0], -2.0f32);
    }
}
Run Code Online (Sandbox Code Playgroud)

(游乐场)

Bur*_*hi5 11

我确实可以确认此代码导致断言在释放模式下跳闸:

$ cargo run --release
    Finished release [optimized] target(s) in 0.00s
     Running `target/release/so53831502`
ar: __m256(2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
br: __m256(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
abr: __m256(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
abr: __m256(-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0)
ab: [-1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0]
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `-1.0`,
 right: `-2.0`', src/main.rs:24:9
Run Code Online (Sandbox Code Playgroud)

这似乎是一个编译器错误,请参见此处此处.特别是,你在呼唤像程序_mm256_set1_ps_mm256_fmadd_ps,这需要的CPU功能avx,并fma分别,但无论你的代码和你的编译命令指示这样的特征,应使用的编译器.

修复此问题的一种方法是告诉编译器在启用avxfma启用功能的情况下编译整个程序,如下所示:

$ RUSTFLAGS="-C target-feature=+avx,+fma" cargo run --release
   Compiling so53831502 v0.1.0 (/tmp/so53831502)
    Finished release [optimized] target(s) in 0.36s
     Running `target/release/so53831502`
ar: __m256(2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
br: __m256(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
abr: __m256(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
abr: __m256(-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
ab: [-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
Run Code Online (Sandbox Code Playgroud)

另一种实现相同结果的方法是告诉编译器使用CPU上所有可用的CPU功能:

$ RUSTFLAGS="-C target-cpu=native" cargo run --release
   Compiling so53831502 v0.1.0 (/tmp/so53831502)
    Finished release [optimized] target(s) in 0.34s
     Running `target/release/so53831502`
ar: __m256(2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
br: __m256(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
abr: __m256(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
abr: __m256(-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
ab: [-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
Run Code Online (Sandbox Code Playgroud)

但是,这两个编译命令都会生成只能在支持avxfma功能的CPU上运行的二进制文件.如果这对您来说不是问题,那么这是一个很好的解决方案.如果您希望构建可移植的二进制文件,则可以在运行时执行CPU功能检测,并在启用特定CPU功能的情况下编译某些功能.因此,您有责任保证只有在启用并可用相应的CPU功能时才会调用所述功能.此过程记录为文档的动态CPU功能检测部分std::arch.

这是一个使用运行时CPU特征检测的示例:

use std::arch::x86_64::*;
use std::process;

fn main() {
    if is_x86_feature_detected!("avx") && is_x86_feature_detected!("fma") {
        // SAFETY: This is safe because we're guaranteed to support the
        // necessary CPU features.
        unsafe { doit(); }
    } else {
        eprintln!("unsupported CPU");
        process::exit(1);
    }
}

#[target_feature(enable = "avx,fma")]
unsafe fn doit() {
    let a = vec![2.0f32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
    let b = -1.0f32;

    let ar = _mm256_loadu_ps(a.as_ptr());
    println!("ar: {:?}", ar);

    let br = _mm256_set1_ps(b);
    println!("br: {:?}", br);

    let mut abr = _mm256_setzero_ps();
    println!("abr: {:?}", abr);

    abr = _mm256_fmadd_ps(ar, br, abr);
    println!("abr: {:?}", abr);

    let mut ab = [0.0; 8];
    _mm256_storeu_ps(ab.as_mut_ptr(), abr);
    println!("ab: {:?}", ab);

    assert_eq!(ab[0], -2.0f32);
}
Run Code Online (Sandbox Code Playgroud)

要运行它,您不再需要设置任何编译标志:

$ cargo run --release
   Compiling so53831502 v0.1.0 (/tmp/so53831502)
    Finished release [optimized] target(s) in 0.29s
     Running `target/release/so53831502`
ar: __m256(2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
br: __m256(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
abr: __m256(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
abr: __m256(-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
ab: [-2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
Run Code Online (Sandbox Code Playgroud)

如果你运行在CPU上生成的二进制文件不支持或者avx还是fma,那么程序应该与一个错误信息退出:unsupported CPU.

总的来说,我认为std::arch可以改进文档.特别是,需要拆分代码的关键边界取决于矢量类型是否出现在函数签名中.也就是说,doit例程不需要超出标准x86(或x86_64)函数ABI的任何调用,因此可以安全地从不支持avx或不支持的函数调用fma.但是,在内部,该函数已被告知使用基于给定CPU功能的附加指令集扩展来编译其代码.这是通过target_feature属性实现的.例如,如果您提供了错误的目标功能:

#[target_feature(enable = "ssse3")]
unsafe fn doit() {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后程序表现出与初始程序相同的行为.

  • @Shepmaster非常合法.面对UB,Rust计划甚至可以从宜家购买床单并吃掉它们. (6认同)