Умножение SSE 16 x uint8_t

Я хочу умножить с SSE4 a __m128i объект с 16 неподписанными 8-битными целыми числами, но я мог найти только внутреннее значение для умножения 16-битных целых чисел. Нет ничего такого, как _mm_mult_epi8?

Ответ 1

В MMX/SSE/AVX нет 8-битного умножения. Тем не менее, вы можете эмулировать 8-битное внутреннее умножение с использованием 16-битного умножения следующим образом:

inline __m128i _mm_mullo_epi8(__m128i a, __m128i b)
{
    __m128i zero = _mm_setzero_si128();
    __m128i Alo = _mm_cvtepu8_epi16(a);
    __m128i Ahi = _mm_unpackhi_epi8(a, zero);
    __m128i Blo = _mm_cvtepu8_epi16(b);
    __m128i Bhi = _mm_unpackhi_epi8(b, zero);
    __m128i Clo = _mm_mullo_epi16(Alo, Blo);
    __m128i Chi = _mm_mullo_epi16(Ahi, Bhi);
    __m128i maskLo = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 14, 12, 10, 8, 6, 4, 2, 0);
    __m128i maskHi = _mm_set_epi8(14, 12, 10, 8, 6, 4, 2, 0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
    __m128i C = _mm_or_si128(_mm_shuffle_epi8(Clo, maskLo), _mm_shuffle_epi8(Chi, maskHi));

     return C;
}

Ответ 2

A (потенциально) быстрее, чем решение Marat на основе Решение Agner Fog:

Вместо расщепления hi/low разбить нечетный/четный. Это имеет дополнительное преимущество, что оно работает с чистым SSE2 вместо того, чтобы требовать SSE4.1 (бесполезно для OP, но для некоторых это хороший дополнительный бонус). Я также добавил оптимизацию, если у вас есть AVX2. Технически оптимизация AVX2 работает только с внутренними функциями SSE2, но медленнее, чем решение влево и вправо.

__m128i mullo_epi8(__m128i a, __m128i b)
{
    // unpack and multiply
    __m128i dst_even = _mm_mullo_epi16(a, b);
    __m128i dst_odd = _mm_mullo_epi16(_mm_srli_epi16(a, 8),_mm_srli_epi16(b, 8));
    // repack
#ifdef __AVX2__
    // only faster if have access to VPBROADCASTW
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_and_si128(dst_even, _mm_set1_epi16(0xFF)));
#else
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_srli_epi16(_mm_slli_epi16(dst_even,8), 8));
#endif
}

Agner использует blendv_epi8 встроенную поддержку SSE4.1.

Edit:

Интересно, что после выполнения дополнительной работы по разборке (с оптимизированными сборками), по крайней мере, мои две реализации скомпилированы точно так же. Пример разборки таргетинга "ivy-bridge" (AVX).

vpmullw xmm2,xmm0,xmm1
vpsrlw xmm0,xmm0,0x8
vpsrlw xmm1,xmm1,0x8
vpmullw xmm0,xmm0,xmm1
vpsllw xmm0,xmm0,0x8
vpand xmm1,xmm2,XMMWORD PTR [rip+0x281]
vpor xmm0,xmm0,xmm1

Он использует версию с оптимизацией AVX2 с предварительно скомпилированной 128-битной константой xmm. Компиляция только с поддержкой SSE2 дает аналогичные результаты (хотя и с использованием инструкций SSE2). Я подозреваю, что оригинальное решение Agner Fog может быть оптимизировано для одной и той же вещи (было бы безумным, если бы не было). Не знаю, как оригинальное решение Marat сравнивается в оптимизированной сборке, хотя для меня достаточно одного метода для всех расширений sim x86, более новых, чем SSE2 и включая SSE2.

Ответ 3

Единственная 8-разрядная команда умножения SSE PMADDUBSW (SSSE3 и более поздние версии, C/С++ intrinsic: _mm_maddubs_epi16). Это умножает 16 x 8-битные значения без знака на 16 x 8-разрядных знаковых значений, а затем суммирует смежные пары, чтобы дать 8 x 16-битных подписанных результатов. Если вы не можете использовать эту довольно специализированную инструкцию, вам нужно распаковать пары из 16-битных векторов и использовать обычные 16-разрядные команды умножения. Очевидно, что это подразумевает как минимум 2x пропускную способность, поэтому используйте 8-битное умножение, если возможно.