SIMD代码可在Debug中使用,但在Release中不可用

时间:2018-12-18 10:57:47

标签: rust simd

此代码可在调试模式下工作,但由于处于释放模式下的断言而出现恐慌。

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);
    }
}

Playground

1 个答案:

答案 0 :(得分: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

这似乎是编译器错误,请参见herehere。特别是,您正在调用诸如_mm256_set1_ps_mm256_fmadd_ps之类的例程,它们分别需要CPU功能avxfma,但是您的代码和编译命令均未向编译器指示应该使用这些功能。

解决此问题的一种方法是告诉编译器在启用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]

获得相同结果的另一种方法是告诉编译器使用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]

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

以下是使用运行时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);
}

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

$ 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]

如果在不支持avxfma的CPU上运行生成的二进制文件,则程序应退出并显示错误消息:unsupported CPU

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

target_feature

然后该程序表现出与您的初始程序相同的行为。