Как я могу оптимизировать эту простую многозначную сим-карту/трансляцию?

Я хочу расширить некоторые u8 до u64, за исключением того, что вместо расширения нуля или знака, которые имеют прямую поддержку, я хочу «расширение копирования». Как лучше всего это сделать (на процессоре Intel с avx512)? Пример кода написан на Rust, но основной язык не представляет интереса.

#![feature(portable_simd)]

use std::simd::*;

// Expands out each input byte 8 times
pub fn batch_splat_scalar(x: [u8; 16]) -> [u64; 16] {
  let mut ret = [0; 16];
  for i in 0..16 {
    ret[i] =
      u64::from_le_bytes([x[i], x[i], x[i], x[i], x[i], x[i], x[i], x[i]]);
  }
  ret
}

pub fn batch_splat_simd(x: u8x16) -> u64x16 {
  Simd::from_array(batch_splat_scalar(x.to_array()))
}

который компилируется примерно так с помощью avx512

        vpmovzxbq       zmm0, qword ptr [rsi]
        vpbroadcastq    zmm1, qword ptr [rip + .LCPI0_0]
        mov     rax, rdi
        vpmuludq        zmm2, zmm0, zmm1
        vpbroadcastq    zmm3, qword ptr [rip + .LCPI0_1]
        vpmuludq        zmm0, zmm0, zmm3
        vpsllq  zmm0, zmm0, 32
        vporq   zmm0, zmm2, zmm0
        vmovdqu64       zmmword ptr [rdi], zmm0
        vpmovzxbq       zmm0, qword ptr [rsi + 8]
        vpmuludq        zmm1, zmm0, zmm1
        vpmuludq        zmm0, zmm0, zmm3
        vpsllq  zmm0, zmm0, 32
        vporq   zmm0, zmm1, zmm0
        vmovdqu64       zmmword ptr [rdi + 64], zmm0
        vzeroupper
        ret

https://godbolt.org/z/67cW5GnKf

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
0
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Значит, каждый элемент результата u64 содержит 8 копий соответствующего ввода u8? Я думаю, что лучше всего в asm vpermb использовать AVX-512VBMI (Ice Lake). При правильном векторе управления вы можете заставить каждый байт ZMM захватывать нужный вам байт из младших 16 байтов другого ZMM (т. е. XMM).

В противном случае транслируйте и vpshufb zmm. (https://www.felixcloutier.com/x86/pshufb)

Одна широковещательная загрузка размером от 128 до 512 бит может обеспечить два тасования с разными векторами управления. Или две vpbroadcastq 64-битные широковещательные загрузки могут передавать две vpshufb с одним и тем же вектором управления.

По крайней мере, на Intel широковещательная загрузка — это просто загрузка, а не ALU. (https://uops.info/). Поэтому, если вы все равно загружаете данные из памяти, выполните одну широковещательную загрузку и используйте 2x vpshufb вместо 2x vpermb, поскольку это дешевле (меньшая задержка, но по-прежнему только один порт выполнения).


Я не знаком с std::simd в Rust, но ассемблерный код, который он генерирует, очень плох, поскольку вместо инструкций перемешивания используются множественные битхаки (вероятно, с константами вроде 0x0101010101010101).

Ассе, который вам нужен, это что-то вроде

   VBROADCASTI32X4  zmm0,  [rsi]   # the mem operand is 128-bit, an xmmword
   vpshufb          zmm1, zmm0, [.LC0]  # or with the vector constants in regs if reused
   vpshufb          zmm0, zmm0, [.LC1] 

Первая векторная константа — 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1 и т. д. Вторая — 8,8,8,8,8. ,8,8,8, 9,9,9,9,9,9,9,9 и т. д. Индексация происходит внутри каждой 128-битной полосы, поэтому мы использовали широковещательную загрузку.

Если бы мы использовали vpermb, мы могли бы использовать простой vmovdqu xmm0, [rsi], который экономит пару байт размера машинного кода, но при перетасовке будет более высокая задержка (но все равно та же пропускная способность, в том числе, по-видимому, в Zen 4). сложнее для выполнения вне очереди, что снижает общую пропускную способность.

Если ваши данные изначально уже находились в нижней части векторной регистрации, вы бы предпочли vpermb перетасовке ALU для трансляции или vpmovzx ее.


Я надеялся, что VPMULTISHIFTQB с 64-битным операндом источника широковещательной памяти будет еще лучше, но, очевидно, на процессорах Intel он не может микро-сплавиться в один цикл загрузки+перетасовки. Таким образом, использование его дважды не лучше, чем 2x vpbroadcastq загрузки плюс 2x vpshufb, за исключением небольшой экономии в размере машинного кода и различной упаковки в кэш uop, что может быть хуже или лучше. uops.info измерил, что vpmultishiftqb составляет 2 мопса для интерфейса на Ice Lake и Alder Lake (Sapphire Rapids). Это может быть победой над Zen 4, где он объединяется в единый моп.

Другие вопросы по теме