эффективный точечный продукт SIMD в ржавчине

Я пытаюсь создать эффективную SIMD-версию точечного продукта для реализации 2D-свертки для типа i16 для FIR-фильтра.

#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;

#[target_feature(enable  = "avx2")]
unsafe fn dot_product(a: &[i16], b: &[i16]) {
    let a = a.as_ptr() as *const [i16; 16];
    let b = b.as_ptr() as *const [i16; 16];
    let a = std::mem::transmute(*a);
    let b = std::mem::transmute(*b);
    let ms_256 = _mm256_mullo_epi16(a, b);
    dbg!(std::mem::transmute::<_, [i16; 16]>(ms_256));
    let hi_128 = _mm256_castsi256_si128(ms_256);
    let lo_128 = _mm256_extracti128_si256(ms_256, 1);
    dbg!(std::mem::transmute::<_, [i16; 8]>(hi_128));
    dbg!(std::mem::transmute::<_, [i16; 8]>(lo_128));
    let temp = _mm_add_epi16(hi_128, lo_128);
}

fn main() {
    let a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    let b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    unsafe {
        dot_product(&a, &b);
    }
}
I] ~/c/simd (master|…) $ env RUSTFLAGS="-C target-cpu=native" cargo run --release | wl-copy
warning: unused variable: `temp`
  --> src/main.rs:16:9
   |
16 |     let temp = _mm_add_epi16(hi_128, lo_128);
   |         ^^^^ help: if this is intentional, prefix it with an underscore: `_temp`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

    Finished release [optimized] target(s) in 0.00s
     Running `target/release/simd`
[src/main.rs:11] std::mem::transmute::<_, [i16; 16]>(ms_256) = [
    0,
    1,
    4,
    9,
    16,
    25,
    36,
    49,
    64,
    81,
    100,
    121,
    144,
    169,
    196,
    225,
]
[src/main.rs:14] std::mem::transmute::<_, [i16; 8]>(hi_128) = [
    0,
    1,
    4,
    9,
    16,
    25,
    36,
    49,
]
[src/main.rs:15] std::mem::transmute::<_, [i16; 8]>(lo_128) = [
    64,
    81,
    100,
    121,
    144,
    169,
    196,
    225,
]

Хотя я концептуально понимаю SIMD, я не знаком с точными инструкциями и внутренними функциями.

Я знаю, что мне нужно, чтобы умножить два вектора, а затем суммировать их по горизонтали, разделив их пополам и используя инструкции для вертикального добавления двух половин меньшего размера.

Я нашел инструкцию madd, которая предположительно должна сделать одно такое суммирование сразу после умножения, но не знаю, что делать с результатом.

Если использовать mul вместо madd, я не уверен, какие инструкции использовать, чтобы еще больше уменьшить результат.

Любая помощь приветствуется!

PS Я пробовал pack_simd но вроде не работает на стабильной ржавчине.


person user1685095    schedule 31.01.2021    source источник
comment
Для больших массивов вы суммируете по вертикали результаты vpmaddwd в векторный аккумулятор с _mm256_add_epi32, и только последнее добавление по горизонтали (одного или пары векторов) в конце. (См. Самый быстрый способ выполнить горизонтальную векторную сумму SSE (или другое сокращение), предполагая, что вы можете адаптировать встроенные функции C / C ++ к Rust). Если вам нужно избежать переполнения 32-битных сумм, вам нужно снова расширить после pmaddwd внутри цикла.   -  person Peter Cordes    schedule 01.02.2021
comment
@PeterCordes Как я уже сказал, я делаю точечный продукт для двумерной свертки в FIR-фильтре. Итак, одна из последовательностей - это ядро ​​фильтра, которое будет иметь что-то вроде 16 или 32 ответвлений.   -  person user1685095    schedule 01.02.2021
comment
Мой опыт работы с SIMD и AVX не включал КИХ-фильтры или подобную обработку сигналов, так что это не говорило мне, с какой длиной скалярного произведения мы имеем дело. Я думаю, вы подразумеваете, что есть 16 или 32 элемента для суммирования, так что это более 1 вектора SIMD, поэтому то, что я сказал, все еще применимо (за исключением того, что вам не нужен цикл, просто полностью разверните): вертикальное добавление результатов vpmaddwd до тех пор, пока у вас один вектор, потом по горизонтали уменьшаем. Или, если вам нужно избежать переполнения в 32-битных суммах 16-битных продуктов, в какой-то момент расширьте их до 64-битных.   -  person Peter Cordes    schedule 01.02.2021
comment
@PeterCordes as *const [i16; 16]; это говорит, что это вектор или 16 16-битных целых чисел. Я не уверен, почему для горизонтального суммирования 16-битного вектора мне понадобится более 1 вектора SIMD. Не могли бы вы уточнить? Может, напишем код на площадке для ржавчины?   -  person user1685095    schedule 01.02.2021
comment
Я не очень хорошо знаю Rust, я здесь по тегу [simd]. Кроме того, я подумал, что это может быть функция с одним вектором, которую вы собираетесь вызывать повторно в цикле. В любом случае, да, только с 16x 2 = 32 байтами входных данных у вас будет только один результат vpmaddwd, если вы можете использовать AVX2. суммируйте его, многократно извлекая старшую половину и добавляя, как в связанных вопросах и ответах в моем первом комментарии.   -  person Peter Cordes    schedule 01.02.2021
comment
@PeterCordes Можете ли вы показать код на C со встроенными функциями? Я не уверен, как многократно вводить hsum и извлекать высокую половину. Спасибо за вашу помощь!   -  person user1685095    schedule 01.02.2021
comment
Я не знаю, насколько хорошо работает автоматический векторизатор ржавчины, но в C / C ++ вы очень часто можете скомпилировать (на современном компиляторе) простую реализацию с -O3 и некоторой целевой спецификацией (-march=native при сборке для вашей локальной машины), и вы получите довольно приличную сборку: godbolt.org/z/zdEoz7. Вы можете найти соответствующую встроенную функцию для каждой сгенерированной инструкции, если хотите преобразовать ее во встроенную функцию.   -  person chtz    schedule 01.02.2021
comment
@chtz Есть. rust.godbolt.org/z/xEY3v1   -  person Angelicos Phosphoros    schedule 01.02.2021
comment
@AngelicosPhosphoros Отлично, я думаю, если вы бросите a и b в i32 перед умножением, вы можете даже получить vpmaddwd (я не знаю достаточно ржавчины, чтобы попробовать это ...)   -  person chtz    schedule 01.02.2021
comment
Самый быстрый способ выполнить горизонтальную векторную сумму SSE (или другое сокращение) имеет C со встроенными функциями и ссылками на другие размеры, например Самый быстрый способ вычисления суммы всех упакованных 32-битных целых чисел с использованием AVX512 или AVX2. Вот почему я в первую очередь связал это для вас.   -  person Peter Cordes    schedule 01.02.2021