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

(Playground)


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