为什么在调试和发布模式下存储和加载AVX2 256位向量会产生不同的结果?

Nic*_*ock 13 simd compiler-optimization rust avx2

当我尝试在AVX2 256位向量中存储加载 256位时,我在发布模式下没有收到预期的输出.

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

fn main() {
    let key = [1u64, 2, 3, 4];
    let avxreg = unsafe { _mm256_load_si256(key.as_ptr() as *const __m256i) };
    let mut back_key = [0u64; 4];
    unsafe { _mm256_storeu_si256(back_key.as_mut_ptr() as *mut __m256i, avxreg) };
    println!("back_key: {:?}", back_key);
}
Run Code Online (Sandbox Code Playgroud)

操场

在调试模式下:

back_key: [1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

在发布模式下:

back_key: [1, 2, 0, 0]
Run Code Online (Sandbox Code Playgroud)

后半部分没有被加载或存储,我无法弄清楚哪一个.

什么是奇怪的是针对本机CPU工作.在发布模式+RUSTFLAGS="-C target-cpu=native"

back_key: [1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

我甚至试图通过强制对齐无法获得Clippy错误(我不确定下面的代码是否被认为更正确).

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

#[repr(align(256))]
#[derive(Debug)]
struct Key([u64; 4]);

fn main() {
    let key = Key([1u64, 2, 3, 4]);
    let avxreg = unsafe { _mm256_load_si256(&key as *const _ as *const __m256i) };
    let mut back_key = Key([0u64; 4]);
    unsafe { _mm256_storeu_si256((&mut back_key) as *mut _ as *mut __m256i, avxreg) };
    println!("back_key: {:?}", back_key);
}
Run Code Online (Sandbox Code Playgroud)
  1. 为什么会这样?
  2. 是否有针对此特定用例的修复程序?
  3. 这个修复是否可以针对用户输入进行推广(例如:如果我想将一个字节切片作为用户输入并执行相同的过程)

Nic*_*ock 3

在更彻底地阅读文档之后,很明显我必须将主体提取到另一个函数中,并通过使用注释强制该函数使用 AVX2 进行编译

#[target_feature(enable = "avx2")]
Run Code Online (Sandbox Code Playgroud)

或者编译整个程序

RUSTFLAGS="-C target-feature=+avx2" cargo run --release
Run Code Online (Sandbox Code Playgroud)

第一个选项更好,因为它保证函数中使用的 SIMD 指令被正确编译,调用者只需在调用is_x86_feature_detected!("avx2"). 所有这些都已记录在案,但如果编译器可以警告“嘿,这个函数使用 AVX2 指令,但没有注释,并且#[target_feature(enable = "avx2")]程序不是在全局启用 AVX2 的情况下编译的,所以调用这个函数是未定义的行为”,那就太神奇了。这会让我省去很多头痛!

由于依赖未定义的行为是不好的,我们在操场上的初始程序应该写成:

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

fn main() {
    unsafe { run() }
}

#[target_feature(enable = "avx2")]
unsafe fn run() {
    let key = [1u64, 2, 3, 4];
    let avxreg = _mm256_load_si256(key.as_ptr() as *const __m256i);
    let mut back_key = [0u64; 4];
    _mm256_storeu_si256(back_key.as_mut_ptr() as *mut __m256i, avxreg);
    println!("back_key: {:?}", back_key);
}
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

  1. main不可能不安全,因此不能用 注释target_feature,因此需要提取到另一个函数中
  2. 这仍然假设x86_64运行代码的 CPU 有avx能力,所以请确保在调用之前进行检查
  3. 不值得研究为什么调试版本会给出正确的结果,因为在我的家用计算机上在发布版本下运行它也会给出正确的结果(在某些咒语下)。查看汇编表明 LLVM 以某种方式进行了优化,但并不是特别有洞察力。