Код SIMD работает в Debug, но не в Release

Этот код работает в режиме отладки, но вызывает панику из-за утверждения в режиме выпуска.

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

(площадка)


person kali    schedule 18.12.2018    source источник
comment
Может быть связано: github.com/rust-lang/rust/issues/50154 или github.com/rust-lang/rust/issues/56950   -  person hellow    schedule 18.12.2018
comment
На самом деле я только что подал вторую. Но да, хороший звонок.   -  person kali    schedule 18.12.2018
comment
Могу подтвердить, я также получаю разные (неправильные) результаты для _mm256_fmadd_ps в сборках выпуска.   -  person Peter Hall    schedule 18.12.2018
comment
Мне кажется, что это UB. Вы не можете использовать встроенные функции поставщика, если код не скомпилирован с соответствующими функциями ЦП.   -  person BurntSushi5    schedule 18.12.2018
comment
Я могу подтвердить, если вы включите флаг функции, он больше не будет паниковать ...   -  person hellow    schedule 18.12.2018
comment
@kali Я ответил на твой вопрос? Если да, отметьте это как ответ. Если нет, что еще вы хотели бы знать?   -  person BurntSushi5    schedule 19.12.2018
comment
Да, @ BurntSushi5, на самом деле мне просто нужно, чтобы вы забыли, что маркеры функций отклоняются, поэтому намек на проблему с github было достаточно. Но спасибо за обширный ответ, в какой-то момент он обязательно поможет кому-то другому.   -  person kali    schedule 20.12.2018


Ответы (1)


Я действительно могу подтвердить, что этот код вызывает отключение 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

Похоже, это ошибка компилятора, см. здесь и здесь. В частности, вы вызываете такие подпрограммы, как _mm256_set1_ps и _mm256_fmadd_ps, для которых требуются функции ЦП avx и fma соответственно, но ни ваш код, ни ваша команда компиляции не указывают компилятору, что такие функции следует использовать.

Один из способов исправить это - указать компилятору скомпилировать всю программу с включенными функциями avx и fma, например:

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

Другой подход, позволяющий добиться того же результата, - указать компилятору использовать все доступные функции ЦП на вашем ЦП:

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

Однако обе эти команды компиляции создают двоичные файлы, которые могут работать только на процессорах, поддерживающих функции avx и fma. Если для вас это не проблема, то это прекрасное решение. Если вместо этого вы хотите создать переносимые двоичные файлы, вы можете выполнить обнаружение функций ЦП во время выполнения и скомпилировать определенные функции с включенными конкретными функциями ЦП. В таком случае вы обязаны гарантировать, что указанные функции будут вызываться только тогда, когда соответствующая функция ЦП включена и доступна. Этот процесс задокументирован как часть динамического ЦП. определение функций std::arch документов.

Вот пример, в котором используется определение функции ЦП во время выполнения:

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, программа должна завершиться с сообщением об ошибке: unsupported CPU.

В целом, я думаю, что документацию для std::arch можно улучшить. В частности, ключевая граница, по которой вам нужно разделить код, зависит от того, присутствуют ли ваши векторные типы в сигнатуре вашей функции. То есть процедура doit не требует для вызова ничего, кроме стандартной функции x86 (или x86_64) ABI, и поэтому ее можно безопасно вызывать из функций, которые иначе не поддерживают avx или fma. Однако внутри функции было сказано скомпилировать свой код с использованием дополнительных расширений набора инструкций, основанных на заданных характеристиках ЦП. Это достигается с помощью атрибута target_feature. Если вы, например, указали неверную целевую функцию:

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

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

person BurntSushi5    schedule 18.12.2018
comment
ваш код демонстрирует неопределенное поведение, и, таким образом, результат является технически законным - вы имели в виду ... незаконным? - person Shepmaster; 18.12.2018
comment
@Shepmaster в значительной степени законный. Перед лицом UB программа Rust могла даже покупать простыни в IKEA и есть их. - person E_net4 the curator; 18.12.2018
comment
@Shepmaster Ха, да, E_net4 прав. Начиная с его UB, любой результат технически легален. - person BurntSushi5; 18.12.2018
comment
Для всех: я разговаривал с Алексом Крайтоном, и он считает, что это не UB, а на самом деле ошибка компилятора. См. Редактирование в ответе выше. Однако обратите внимание, что остальная часть моего ответа по-прежнему применима - если вы используете встроенные функции поставщика, вам необходимо указать компилятору построить ваш код с использованием этих функций. В противном случае вы можете столкнуться с ошибками производительности или ошибками времени выполнения, такими как SIGILL. - person BurntSushi5; 20.12.2018