2012-06-07 4 views
11

Я пытаюсь найти уменьшение суммы из 32 элементов (каждый 1 байт данных) на процессоре Intel i3. Я сделал это:Суммирование сокращения беззнаковых байтов без переполнения с использованием SSE2 на Intel

s=0; 
for (i=0; i<32; i++) 
{ 
    s = s + a[i]; 
} 

Однако его занимать больше времени, так как мое приложение в режиме реального времени приложение требует гораздо меньше времени. Обратите внимание, что окончательная сумма может быть больше 255.

Есть ли способ, которым я могу реализовать это, используя инструкции низкого уровня SIMD SSE2? К сожалению, я никогда не использовал SSE. Я попытался найти функцию sse2 для этой цели, но она также недоступна. Гарантируется ли (sse) сокращение времени вычислений для таких небольших проблем?

Любые предложения?

Примечание: Я реализовал аналогичные алгоритмы с использованием OpenCL и CUDA, и это сработало отлично, но только тогда, когда размер проблемы был большим. Для небольших проблем стоимость накладных расходов была больше. Не знаете, как это работает на SSE.

+0

Это сумма больше 255? – hirschhornsalz

+0

Да, окончательная сумма может быть больше 255 – gpuguy

ответ

7

Вы можете использовать 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 

Покушение версия встроенных функций:

Я никогда не использую встроенные функции так что этот код, вероятно, не имеет никакого смысла. Хотя разборка выглядела нормально.

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]; 
} 
+0

Не могли бы вы назвать встроенные эквиваленты компилятора Intel® C++ для вышеперечисленного? – gpuguy

+0

@gpuguy Я пробовал, но я никогда не использовал intrinsics, поэтому я, наверное, что-то испортил. То, что 'reinterpret_cast' тоже выглядит не очень хорошо, но я не мог понять, как избавиться от него. – harold

+0

Использовать этот же трюк для 'int8_t' (вместо' uint8_t'): сдвиг диапазона в unsigned (xor с 0x80), а затем вычесть «16 * 0x80» из общего числа. См. [Этот патч для векторной библиотеки классов Agner Fog для примера с intrinsics] (https://github.com/pcordes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b). Эта же идея работает для [вектора ymm AVX2] (https://github.com/pcordes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272)). –

5

Это немного многословно, но оно должно быть по крайней мере, 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.

+0

Как убедиться, что [] выровнено по 16 байт? В SSE есть ли что-то похожее на __align __ (16) в CUDA? – gpuguy

+0

Это зависит от того, какой компилятор и ОС вы используете - например. для gcc с нединамическими выделениями используйте '__attribute__ ((aligned (16)))' - для динамических распределений в Linux используйте 'memalign()' или 'posix_memalign()'. –

+0

придется сократить это; он работает, но 'psadbw' является правильным ответом. Для подписанного 'int8_t' вы можете сменять диапазон на unsigned с помощью' xor', чтобы добавить 0x80 в каждый байт, и вычесть «16 * 0x80» из результата. (См. [Этот патч для векторной библиотеки классов Agner Fog] (https: // github.com/pcordes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b), например, с внутренними функциями. Эта же идея работает для [вектора ymm AVX2] (https://github.com/pcordes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272)). Но ОП здесь, похоже, уже без подписи, поэтому psadbw - огромная победа. –

 Смежные вопросы

  • Нет связанных вопросов^_^