这段代码在调试模式下工作,但由于在发布模式下使用了assert,所以会出现panic .

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)

推荐答案

我确实可以确认,这段代码导致assert在释放模式下跳闸:

$ 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这样的 routine ,它们分别需要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]

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

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

#[target_feature(enable = "ssse3")]
unsafe fn doit() {
    // ...
}

然后该程序显示出与初始程序相同的行为.

Rust相关问答推荐

如何最好地并行化修改同一Rust向量的多个切片的代码?

无法实现整型类型的泛型FN

如何在Rust中基于字符串 Select struct ?

为什么 tokio 在以奇怪的方式调用时只运行 n 个任务中的 n-1 个?

为什么我需要 to_string 函数的参考?

Cargo.toml:如何有条件地启用依赖项功能?

类型生命周期绑定的目的是什么?

为什么实现特征的对象期望比具体对象有更长的生命周期?

Rust LinkedList 中的borrow 判断器错误的原因是什么?

Rust 如何将链表推到前面?

如何保存指向持有引用数据的指针?

注释闭包参数强调使用高阶排定特征界限

我的 Axum 处理程序无法编译:未实现 IntoResponse 特征

Rust 函数指针似乎被borrow 判断器视为有状态的

在 Rust 中,将可变引用传递给函数的机制是什么?

无法把握借来的价值不够长寿,请解释

在 Rust 中为泛型 struct 编写一次特征绑定

意外的正则表达式模式匹配

相交着色器从 SSBO 中读取零

为移动和借位的所有组合实现 Add、Sub、Mul、Div