Суммирование сокращения беззнаковых байтов без переполнения с использованием SSE2 на Intel
Я пытаюсь найти сокращение суммы из 32 элементов (каждый 1 байт данных) на процессоре Intel i3. Я сделал это:
s=0;
for (i=0; i<32; i++)
{
s = s + a[i];
}
Однако, это занимает больше времени, так как мое приложение - приложение в режиме реального времени, требующее гораздо меньшего времени. Обратите внимание, что итоговая сумма может быть больше 255.
Есть ли способ реализовать это с помощью инструкций SMD SSE2 низкого уровня? К сожалению, я никогда не использовал SSE. Я попытался найти функцию sse2 для этой цели, но она также недоступна. Гарантируется ли (sse) сокращение времени вычислений для таких небольших проблем?
Любые предложения?
Примечание. Я реализовал аналогичные алгоритмы с использованием OpenCL и CUDA, и это сработало отлично, но только тогда, когда размер проблемы был большим. Для небольших проблем стоимость накладных расходов была больше. Не знаете, как это работает на SSE.
Ответы
Ответ 1
Вы можете злоупотреблять PSADBW
, чтобы быстро вычислить небольшие горизонтальные суммы.
Что-то вроде этого: (не проверено)
pxor xmm0, xmm0
psadbw xmm0, [a + 0]
pxor xmm1, xmm1
psadbw xmm1, [a + 16]
paddw xmm0, xmm1
pshufd xmm1, xmm0, 2
paddw xmm0, xmm1 ; low word in xmm0 is the total sum
Попытка внутренней версии:
Я никогда не использую intrinsics, поэтому этот код, вероятно, не имеет никакого смысла. Однако разборка выглядела нормально.
uint16_t sum_32(const uint8_t a[32])
{
__m128i zero = _mm_xor_si128(zero, zero);
__m128i sum0 = _mm_sad_epu8(
zero,
_mm_load_si128(reinterpret_cast<const __m128i*>(a)));
__m128i sum1 = _mm_sad_epu8(
zero,
_mm_load_si128(reinterpret_cast<const __m128i*>(&a[16])));
__m128i sum2 = _mm_add_epi16(sum0, sum1);
__m128i totalsum = _mm_add_epi16(sum2, _mm_shuffle_epi32(sum2, 2));
return totalsum.m128i_u16[0];
}
Ответ 2
Это немного длинный, но он должен быть как минимум в 2 раза быстрее, чем скалярный код:
uint16_t sum_32(const uint8_t a[32])
{
const __m128i vk0 = _mm_set1_epi8(0); // constant vector of all 0s for use with _mm_unpacklo_epi8/_mm_unpackhi_epi8
__m128i v = _mm_load_si128(a); // load first vector of 8 bit values
__m128i vl = _mm_unpacklo_epi8(v, vk0); // unpack to two vectors of 16 bit values
__m128i vh = _mm_unpackhi_epi8(v, vk0);
__m128i vsum = _mm_add_epi16(vl, vh);
v = _mm_load_si128(&a[16]); // load second vector of 8 bit values
vl = _mm_unpacklo_epi8(v, vk0); // unpack to two vectors of 16 bit values
vh = _mm_unpackhi_epi8(v, vk0);
vsum = _mm_add_epi16(vsum, vl);
vsum = _mm_add_epi16(vsum, vh);
// horizontal sum
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 8));
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 4));
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 2));
return _mm_extract_epi16(vsum, 0);
}
Обратите внимание, что a[]
должно быть выровнено по 16 байт.
Возможно, вы улучшите код выше, используя _mm_hadd_epi16
.