Почему сохранение и загрузка из 256-битного вектора AVX2 дает разные результаты в режимах отладки и выпуска?

Когда я пытаюсь хранить и load 256 бит в 256-битный вектор AVX2 и обратно, я не получаю ожидаемого результата в режиме выпуска.

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

детская площадка

В режиме отладки:

back_key: [1, 2, 3, 4]

В режиме выпуска:

back_key: [1, 2, 0, 0]

Задняя половина либо не загружается, либо не хранится, и я не могу понять, какая именно.

Что странно, так это то, что нацелен на работу собственного процессора. В режиме выпуска + RUSTFLAGS="-C target-cpu=native"

back_key: [1, 2, 3, 4]

Я даже пытался избавиться от ошибок 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);
}
  1. Почему это происходит?
  2. Есть ли исправление для этого конкретного варианта использования?
  3. Можно ли обобщить это исправление для пользовательского ввода (например: если бы я хотел взять байтовый фрагмент в качестве пользовательского ввода и выполнить ту же процедуру)

person Nick Babcock    schedule 20.09.2018    source источник
comment
Вы уверены, что массив из 4 i64 можно рассматривать как 256-битную выровненную ячейку памяти, на которую указывает * a, насколько я знаю, 4 i64 выровнены по i64   -  person Stargateur    schedule 21.09.2018
comment
Даже если выравнивание было проблемой, признаком этого был бы сбой, а не неправильный вывод (vmovaps с невыровненным адресом генерирует ошибку)   -  person harold    schedule 21.09.2018
comment
похоже на ошибку в LLVM   -  person Stargateur    schedule 21.09.2018
comment
println!("{:?}", avxreg); позволяют сказать, что load уже является проблемой, и используйте _mm256_loadu_si256 исправьте ее, но все же магазин по-прежнему выводит неверный результат   -  person Stargateur    schedule 21.09.2018
comment
При внимательном чтении документов, мне кажется, что я должен извлечь это в другой function и используйте #[target_feature(enable = "avx2")], который работает. Я думаю, что это отвечает на вопросы 2, 3, но я не знаю о 1.   -  person Nick Babcock    schedule 21.09.2018
comment
О, хороший, ну, вы можете автоматически ответить вам;), а для пункта 1 спрашивать о неопределенном поведении бесполезно, поскольку все может (и должно) случиться.   -  person Stargateur    schedule 21.09.2018


Ответы (1)


После более тщательного чтения документации стало ясно, что мне нужно извлечь тело в другую функцию и принудительно скомпилировать эту функцию с AVX2, аннотируя ее с помощью

#[target_feature(enable = "avx2")]

Или скомпилируйте всю программу с

RUSTFLAGS="-C target-feature=+avx2" cargo run --release

Первый вариант лучше, потому что он гарантирует, что инструкции 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);
}

Некоторые примечания:

  1. main не может быть небезопасным и, следовательно, не может быть аннотирован с помощью target_feature, поэтому необходимо извлечь в другую функцию
  2. Это по-прежнему предполагает, что x86_64 ЦП, на котором запущен код, имеет avx возможности, поэтому убедитесь, что вы проверили перед вызовом
  3. Не стоит разбираться, почему отладочная версия дает правильные результаты, поскольку запуск ее в выпуске на моем домашнем компьютере также дает правильные результаты (при определенных заклинаниях). Анализ сборки показывает, что LLVM так или иначе оптимизирован, но это не особо проницательно.
person Nick Babcock    schedule 21.09.2018
comment
Я подозреваю, что вы могли бы быть более конкретными. В частности, что-то вроде того, вы не можете вызвать функцию, ABI которой зависит от регистров AVX, из функции, которая сама не скомпилирована для AVX. Итак, в вашем случае main не компилируется с AVX, но вы вызываете подпрограмму, где __m256i появляется в сигнатуре функции. Измененный вами код больше не делает этого, поскольку вектор AVX не отображается в типе run. - person BurntSushi5; 21.09.2018