четверг, 3 апреля 2014 г.

SSE: циклический сдвиг одной командой

Не очень давно работаю с SSE, но, в связи с производственной необходимостью, совершенствую свои навыки и в этом нелегком, на первый взгляд, деле. На повестке дня некоторое время стоял вопрос о циклическом сдвиге вектора. Команд для этого в SSE я не нашел, поэтому пошел классическим путем - в гугл. Гугл мне на это дал интересное решение: три отличные команды - неплохо, но неочевидно.
Копирую четыре варианта правого сдвига (если вдруг потеряется контент по ссылке):
unpckhps xmm1,xmm0
shufps   xmm0,xmm1,0x61
shufps   xmm0,xmm0,0xC6

unpckhps xmm1,xmm0
shufps   xmm0,xmm1,0x64
shufps   xmm0,xmm0,0xD2

unpckhps xmm1,xmm0
shufps   xmm0,xmm1,0x91
shufps   xmm0,xmm0,0x87

unpckhps xmm1,xmm0
shufps   xmm0,xmm1,0x94
shufps   xmm0,xmm0,0x93
Если немного подумать, можно придти к более очевидному аналогу:
_mm_or_si128( _mm_srli_si128( a, 4 * 1 ), _mm_slli_si128( a, 4 * 3 ) )

Тем не менее, это по прежнему три команды. Для себя, я, конечно, оставил это очевидное решение, но запомнил его неэффективность до лучших времен.
А вот не так давно, в ходе очень интересной дискуссии на ХэКе, я как раз проектировал и реализовывал SIMD-алгоритм для вычисления квадранта произвольной точки на произвольном базисе. Когда я упаковал 128-битные квадранты в 32-битные слова, я еще не понимал, что тем самым нашел элегантное решение и для своих циклических сдвигов:

static const __m128i sul_mask[] = {
 _mm_set_epi8( 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ), // 16x0 32x0 64x0
 _mm_set_epi8( 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15 ), // 
 _mm_set_epi8( 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14 ), // 16x1
 _mm_set_epi8( 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13 ), // 
 _mm_set_epi8( 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12 ), // 16x2 32x1
 _mm_set_epi8( 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11 ), // 
 _mm_set_epi8( 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10 ), // 16x3
 _mm_set_epi8( 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9 ), // 
 _mm_set_epi8( 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8 ), // 16x4 32x2 64x1
 _mm_set_epi8( 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7 ), // 
 _mm_set_epi8( 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6 ), // 16x5
 _mm_set_epi8( 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5 ), // 
 _mm_set_epi8( 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4 ), // 16x6 32x3
 _mm_set_epi8( 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3 ), // 
 _mm_set_epi8( 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 ), // 16x7
 _mm_set_epi8( 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ) // 
};
//
_mm_shuffle_epi8( a, sul_mask[(u & 0x03) * 4] );
Вот так, иногда бывает полезно решать чужие задачи, чтобы совершенствовать свои решения.

p.s.: маскирование и умножение индекса u, конечно делать необязательно; если нужен академический результат, можно просто построить индивидуальную таблицу 128-битных масок для всех четырех типов вектора (8x/16x/32x/64x), а в данном случае я использую одну такую таблицу для всех типов.

Комментариев нет:

Отправить комментарий