Я хочу умножить с SSE4 a __m128i
объект с 16 неподписанными 8-битными целыми числами, но я мог найти только внутреннее значение для умножения 16-битных целых чисел. Нет ничего такого, как _mm_mult_epi8
?
Умножение SSE 16 x uint8_t
Ответ 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-битное умножение, если возможно.