Умножение SSE 16 x uint8_t
Я хочу умножить с SSE4 __m128i
объект с 16 беззнаковыми 8-битными целыми числами, но я мог найти только встроенную функцию для умножения 16-битных целых чисел. Нет ничего такого как _mm_mult_epi8
?
3 ответа
В 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;
}
(Потенциально) более быстрый способ, чем решение Марата на основе решения Agner Fog:
Вместо разделения hi / low разделите нечетное / четное. Это дает дополнительное преимущество, заключающееся в том, что он работает с чистым SSE2 вместо того, чтобы требовать SSE4.1 (бесполезный для ОП, но для некоторых это хороший дополнительный бонус). Я также добавил оптимизацию, если у вас есть 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
}
Агнер использует blendv_epi8
встроенный с поддержкой SSE4.1.
Редактировать:
Интересно, что после выполнения большей работы по разборке (с оптимизированными сборками), по крайней мере, две мои реализации будут скомпилированы с одинаковой точностью. Пример разборки прицеливания "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 могло бы быть оптимизировано к тому же самому (было бы сумасшествием, если бы этого не произошло). Не представляю, как оригинальное решение Марата сравнивается в оптимизированной сборке, хотя для меня было бы неплохо иметь единый метод для всех расширений simd x86, более новых, чем SSE2, включая SSE2.
Единственная 8-битная инструкция умножения SSE - это PMADDUBSW (SSSE3 и более поздние, встроенные в C/C++: _mm_maddubs_epi16). Это умножает 16 x 8-битные значения без знака на 16 x 8-битные значения со знаком и затем суммирует соседние пары, чтобы получить 8 x 16-битные результаты со знаком. Если вы не можете использовать эту довольно специализированную инструкцию, вам нужно распаковать ее в пары 16-битных векторов и использовать обычные 16-битные инструкции умножения. Очевидно, это подразумевает как минимум удвоение пропускной способности, поэтому, если возможно, используйте 8-битное умножение.