Умножение SSE 16 x uint8_t - PullRequest
       86

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

11 голосов
/ 19 ноября 2011

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

Ответы [ 3 ]

14 голосов
/ 20 марта 2015

A (потенциально) более быстрый способ, чем решение Марата на основе Решение 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.

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 могло бы быть оптимизировано к тому же самому (было бы сумасшествием, если бы этого не произошло). Не представляю, как оригинальное решение Марата сравнивается в оптимизированной сборке, хотя для меня было бы неплохо иметь единый метод для всех расширений simd x86, более новых, чем SSE2, включая SSE2.

11 голосов
/ 19 ноября 2011

В 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;
}
8 голосов
/ 19 ноября 2011

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

...