транспонировать для 8 регистров 16-битных элементов на SSE2 / SSSE3

(Я новичок в SSE / asm, извиняюсь, если это очевидно или избыточно)

Есть ли лучший способ транспонировать 8 регистров SSE, содержащих 16-битные значения, чем выполнение 24 unpck [lh] ps и 8/16 + перемешивания с использованием 8 дополнительных регистров? (Обратите внимание на использование инструкций до SSSE 3, Intel Merom, также известного как отсутствие BLEND * из SSE4.)

Допустим, у вас есть регистры v [0-7] и вы используете t0-t7 в качестве вспомогательных регистров. В псевдо-внутреннем коде:

/* Phase 1: process lower parts of the registers */
/* Level 1: work first part of the vectors */
/*   v[0]  A0 A1 A2 A3 A4 A5 A6 A7
**   v[1]  B0 B1 B2 B3 B4 B5 B6 B7
**   v[2]  C0 C1 C2 C3 C4 C5 C6 C7
**   v[3]  D0 D1 D2 D3 D4 D5 D6 D7
**   v[4]  E0 E1 E2 E3 E4 E5 E6 E7
**   v[5]  F0 F1 F2 F3 F4 F5 F6 F7
**   v[6]  G0 G1 G2 G3 G4 G5 G6 G7
**   v[7]  H0 H1 H2 H3 H4 H5 H6 H7 */
t0 = unpcklps (v[0], v[1]); /* Extract first half interleaving */
t1 = unpcklps (v[2], v[3]); /* Extract first half interleaving */
t2 = unpcklps (v[4], v[5]); /* Extract first half interleaving */
t3 = unpcklps (v[6], v[7]); /* Extract first half interleaving */
t0 = pshufhw (t0, 0xD8); /* Flip middle 2 high */
t0 = pshuflw (t0, 0xD8); /* Flip middle 2 low */
t1 = pshufhw (t1, 0xD8); /* Flip middle 2 high */
t1 = pshuflw (t1, 0xD8); /* Flip middle 2 low */
t2 = pshufhw (t2, 0xD8); /* Flip middle 2 high */
t2 = pshuflw (t2, 0xD8); /* Flip middle 2 low */
t3 = pshufhw (t3, 0xD8); /* Flip middle 2 high */
t3 = pshuflw (t3, 0xD8); /* Flip middle 2 low */
/*   t0   A0 B0 A1 B1 A2 B2 A3 B3  (A B - 0 1 2 3)
**   t1   C0 D0 C1 D1 C2 D2 C3 D3  (C D - 0 1 2 3)
**   t2   E0 F0 E1 F1 E2 F2 E3 F3  (E F - 0 1 2 3)
**   t3   G0 H0 G1 H1 G2 H2 G3 H3  (G H - 0 1 2 3) */
/* L2 */
t4 = unpcklps (t0, t1);
t5 = unpcklps (t2, t3);
t6 = unpckhps (t0, t1);
t7 = unpckhps (t2, t3);
/*   t4   A0 B0 C0 D0 A1 B1 C1 D1 (A B C D - 0 1)
**   t5   E0 F0 G0 H0 E1 F1 G1 H1 (E F G H - 0 1)
**   t6   A2 B2 C2 D2 A3 B3 C3 D3 (A B C D - 2 3)
**   t7   E2 F2 G2 H2 E3 F3 G3 H3 (E F G H - 2 3) */
/* Phase 2: same with higher parts of the registers */
/*   A    A0 A1 A2 A3 A4 A5 A6 A7
**   B    B0 B1 B2 B3 B4 B5 B6 B7
**   C    C0 C1 C2 C3 C4 C5 C6 C7
**   D    D0 D1 D2 D3 D4 D5 D6 D7
**   E    E0 E1 E2 E3 E4 E5 E6 E7
**   F    F0 F1 F2 F3 F4 F5 F6 F7
**   G    G0 G1 G2 G3 G4 G5 G6 G7
**   H    H0 H1 H2 H3 H4 H5 H6 H7 */
t0 = unpckhps (v[0], v[1]);
t0 = pshufhw (t0, 0xD8); /* Flip middle 2 high */
t0 = pshuflw (t0, 0xD8); /* Flip middle 2 low */
t1 = unpckhps (v[2], v[3]);
t1 = pshufhw (t1, 0xD8); /* Flip middle 2 high */
t1 = pshuflw (t1, 0xD8); /* Flip middle 2 low */
t2 = unpckhps (v[4], v[5]);
t2 = pshufhw (t2, 0xD8); /* Flip middle 2 high */
t2 = pshuflw (t2, 0xD8); /* Flip middle 2 low */
t3 = unpckhps (v[6], v[7]);
t3 = pshufhw (t3, 0xD8); /* Flip middle 2 high */
t3 = pshuflw (t3, 0xD8); /* Flip middle 2 low */
/*   t0   A4 B4 A5 B5 A6 B6 A7 B7  (A B - 4 5 6 7)
**   t1   C4 D4 C5 D5 C6 D6 C7 D7  (C D - 4 5 6 7)
**   t2   E4 F4 E5 F5 E6 F6 E7 F7  (E F - 4 5 6 7)
**   t3   G4 H4 G5 H5 G6 H6 G7 H7  (G H - 4 5 6 7) */
/* Back to first part, v[0-3] can be re-written now */
/* L3 */
v[0] = unpcklpd (t4, t5);
v[1] = unpckhpd (t4, t5);
v[2] = unpcklpd (t6, t7);
v[3] = unpckhpd (t6, t7);
/* v[0] = A0 B0 C0 D0 E0 F0 G0 H0
** v[1] = A1 B1 C1 D1 E1 F1 G1 H1
** v[2] = A2 B2 C2 D2 E2 F2 G2 H2
** v[3] = A3 B3 C3 D3 E3 F3 G3 H3 */
/* Back to second part, t[4-7] can be re-written now... */
/* L2 */
t4 = unpcklps (t0, t1);
t5 = unpcklps (t2, t3);
t6 = unpckhps (t0, t1);
t7 = unpckhps (t2, t3);
/*   t4   A4 B4 C4 D4 A5 B5 C5 D5 (A B C D - 4 5)
**   t5   E4 F4 G4 H4 E5 F5 G5 H5 (E F G H - 4 5)
**   t6   A6 B6 C6 D6 A7 B7 C7 D7 (A B C D - 6 7)
**   t7   E6 F6 G6 H6 E7 F7 G7 H7 (E F G H - 6 7) */
/* L3 */
v[4] = unpcklpd (t4, t5);
v[5] = unpckhpd (t4, t5);
v[6] = unpcklpd (t6, t7);
v[7] = unpckhpd (t6, t7);
/* v[4] = A4 B4 C4 D4 E4 F4 G4 H4
** v[5] = A5 B5 C5 D5 E5 F5 G5 H5
** v[6] = A6 B6 C6 D6 E6 F6 G6 H6
** v[7] = A7 B7 C7 D7 E7 F7 G7 H7 */

Каждый unpck * занимает 3 цикла задержки или 2 для обратной пропускной способности (по данным Агнера). Это убивает большую часть прироста производительности от использования SSE (в этом коде), потому что этот танец регистров занимает почти один цикл на элемент. Я пытался понять файл asm x264 для транспонирования x86, но не смог понять макросы.

Спасибо!


person alecco    schedule 25.03.2010    source источник


Ответы (3)


Да, всего 24 инструкции:

8 x _mm_unpacklo_epi16/_mm_unpackhi_epi16 (PUNPCKLWD/PUNPCKHWD)
8 x _mm_unpacklo_epi32/_mm_unpackhi_epi32 (PUNPCKLDQ/PUNPCKHDQ)
8 x _mm_unpacklo_epi64/_mm_unpackhi_epi64 (PUNPCKLQDQ/PUNPCKHQDQ)

Дайте мне знать, если вам понадобятся подробности, но это довольно очевидно.

person Paul R    schedule 25.03.2010
comment
Отлично, дружище! Не могли бы вы указать мне в каком-нибудь направлении, где найти больше основных преобразований с помощью SSE? - person alecco; 26.03.2010
comment
@aleccolocco: по SSE, к сожалению, не так много хорошего материала, по крайней мере, по более сложным темам. Я рекомендую посмотреть ресурсы AltiVec (например, на developer.apple.com) - многие методы AltiVec легко переводятся на SSE. - person Paul R; 26.03.2010
comment
Хорошие новости: мне это удалось. Плохие новости: прирост производительности всего 5% на 1 млн элементов. Но спасибо, я узнал несколько крутых трюков с SSE! - person alecco; 26.03.2010
comment
@aleccolocco: если вы просто выполняете транспонирование из памяти в память и ничего больше, тогда ваша производительность может быть ограничена пропускной способностью памяти и т. д. - в целом вы получите гораздо лучшую общую производительность, если сможете объединить транспонирование с другими операциями . Также обратите внимание, что производительность SSE сильно различается между разными семействами процессоров: например, до Core 2 Duo = ужасно, Core 2 Duo = хорошо, Core i7 = круто! - person Paul R; 26.03.2010
comment
@ Пол Р Ага. Я реализую Эффективную реализацию сортировки на многоядерной архитектуре ЦП SIMD и некоторые другие вещи. Merom моего ноутбука кажется в 8 раз медленнее, чем их Xeon Penryn, и я даже не хочу знать, насколько он будет быстрее на i7. Тем не менее, элементы 1M должны занимать всего 2 МБ и располагаться внутри L2 (так что, я думаю, это не пропускная способность). Ура! - person alecco; 26.03.2010
comment
@aleccolocco: Ладно, да, тогда, вероятно, ограничивающим фактором является ваш процессор. Также обратите внимание, что планирование инструкций может быть проблемой, если вы используете asm - вы, вероятно, получите лучшие результаты, используя инстринсики C (_mm_unpackX_XXX), и позвольте компилятору C выполнять планирование. ICC - лучший компилятор для этого, за ним следует gcc, за которым следует отвратительная Visual Studio. Кроме того, если вы можете работать на 64-битном процессоре, скомпилируйте с -m64, чтобы вы получили 16 регистров SSE, а не 8. - person Paul R; 26.03.2010
comment
@PaulR: Разница в производительности частично связана с 64-битной стеной в SSE: 128-битный блок SSE формируется из двух отдельных 64-битных блоков, что приводит к штрафу всякий раз, когда данные должны пересекать 64-битную границу. (У каждого 64-битного устройства есть свои регистры) intel.com/technology/itj/2008/v12i3/3-paper/3-super.htm Также попробуйте использовать статическую форму единого назначения, чтобы избежать проблем с ложными зависимостями данных. - person rwong; 24.11.2011
comment
@rwong: это действительно применимо только к старым процессорам (до Core 2) - начиная с Core 2 Intel перешла на полную 128-битную реализацию SSE - person Paul R; 24.11.2011
comment
Поскольку эти функции принимают __m128i, разве этот подход не работает с матрицей с плавающей запятой 8x8? - person DavidS; 01.09.2014
comment
@DavidS: вы обычно выполняете транспонирование 4x4 с поплавками, а не 8x8, поскольку вы получаете только 4 числа с плавающей запятой на вектор (т.е. вы используете 4 вектора). Но принцип тот же, и вы по-прежнему можете использовать целочисленные инструкции SSE для перетасовки. И, конечно же, вы можете создавать большие транспозиции, используя векторную транспонирование 4x4 в качестве строительного блока. - person Paul R; 01.09.2014
comment
@PaulR, да так и думал. Возможно, я могу попробовать использовать AVX-256, так как я могу разместить восемь 32-битных (с плавающей запятой) в каждом регистре .. какие-нибудь мысли по этому поводу? - person DavidS; 02.09.2014
comment
@DavidS: да, вы можете сделать 8x8 с AVX, но это немного сложнее из-за разделенных полос. - person Paul R; 02.09.2014
comment
@PaulR: Я какое-то время возился с этим, но каждый раз терпит неудачу. Очень хотелось бы получить рабочую альтернативу с помощью AVX2. Чтобы не захватить больше этой темы: возможно, я могу связаться с вами? Если вы хотите сохранить конфиденциальность своей электронной почты, вы найдете мою внизу страницы: davidsteinsland.net/ ом-мег - person DavidS; 02.09.2014
comment
@DavidS: Я был бы рад помочь с этим, но я думаю, что вместо того, чтобы отключать его, мы должны создать новый вопрос, чтобы любые решения были доступны другим в будущем. Не стесняйтесь задавать вопрос, например Как выполнить транспонирование 8x8 (с плавающей запятой) с помощью AVX / AVX2? и убедитесь, что вы пометили его как simd, avx и т. д., и тогда я получу автоматическое уведомление и смогу ответить. Вы также можете получить некоторые полезные решения от некоторых других фанатов SIMD здесь одновременно. - person Paul R; 02.09.2014

Мне пришлось сделать это самому, так что вот мой окончательный результат. Обратите внимание, что используемые мной инструкции загрузки и сохранения относятся к 16-байтовым выровненным массивам, которые были объявлены с использованием m128i * array = (__m128i *) _mm_malloc (N * sizeof (uint16_t), 16); ИЛИ int16_t массив [N] __ атрибут ((выровненный (16)));

__m128i *p_input  = (__m128i*)array;
__m128i *p_output = (__m128i*)array;
__m128i a = _mm_load_si128(p_input++);
__m128i b = _mm_load_si128(p_input++);
__m128i c = _mm_load_si128(p_input++);
__m128i d = _mm_load_si128(p_input++);
__m128i e = _mm_load_si128(p_input++);
__m128i f = _mm_load_si128(p_input++);
__m128i g = _mm_load_si128(p_input++);
__m128i h = _mm_load_si128(p_input);

__m128i a03b03 = _mm_unpacklo_epi16(a, b);
__m128i c03d03 = _mm_unpacklo_epi16(c, d);
__m128i e03f03 = _mm_unpacklo_epi16(e, f);
__m128i g03h03 = _mm_unpacklo_epi16(g, h);
__m128i a47b47 = _mm_unpackhi_epi16(a, b);
__m128i c47d47 = _mm_unpackhi_epi16(c, d);
__m128i e47f47 = _mm_unpackhi_epi16(e, f);
__m128i g47h47 = _mm_unpackhi_epi16(g, h);

__m128i a01b01c01d01 = _mm_unpacklo_epi32(a03b03, c03d03);
__m128i a23b23c23d23 = _mm_unpackhi_epi32(a03b03, c03d03);
__m128i e01f01g01h01 = _mm_unpacklo_epi32(e03f03, g03h03);
__m128i e23f23g23h23 = _mm_unpackhi_epi32(e03f03, g03h03);
__m128i a45b45c45d45 = _mm_unpacklo_epi32(a47b47, c47d47);
__m128i a67b67c67d67 = _mm_unpackhi_epi32(a47b47, c47d47);
__m128i e45f45g45h45 = _mm_unpacklo_epi32(e47f47, g47h47);
__m128i e67f67g67h67 = _mm_unpackhi_epi32(e47f47, g47h47);

__m128i a0b0c0d0e0f0g0h0 = _mm_unpacklo_epi64(a01b01c01d01, e01f01g01h01);
__m128i a1b1c1d1e1f1g1h1 = _mm_unpackhi_epi64(a01b01c01d01, e01f01g01h01);
__m128i a2b2c2d2e2f2g2h2 = _mm_unpacklo_epi64(a23b23c23d23, e23f23g23h23);
__m128i a3b3c3d3e3f3g3h3 = _mm_unpackhi_epi64(a23b23c23d23, e23f23g23h23);
__m128i a4b4c4d4e4f4g4h4 = _mm_unpacklo_epi64(a45b45c45d45, e45f45g45h45);
__m128i a5b5c5d5e5f5g5h5 = _mm_unpackhi_epi64(a45b45c45d45, e45f45g45h45);
__m128i a6b6c6d6e6f6g6h6 = _mm_unpacklo_epi64(a67b67c67d67, e67f67g67h67);
__m128i a7b7c7d7e7f7g7h7 = _mm_unpackhi_epi64(a67b67c67d67, e67f67g67h67);

_mm_store_si128(p_output++, a0b0c0d0e0f0g0h0);
_mm_store_si128(p_output++, a1b1c1d1e1f1g1h1);
_mm_store_si128(p_output++, a2b2c2d2e2f2g2h2);
_mm_store_si128(p_output++, a3b3c3d3e3f3g3h3);
_mm_store_si128(p_output++, a4b4c4d4e4f4g4h4);
_mm_store_si128(p_output++, a5b5c5d5e5f5g5h5);
_mm_store_si128(p_output++, a6b6c6d6e6f6g6h6);
_mm_store_si128(p_output, a7b7c7d7e7f7g7h7);
person Stephen Thomas    schedule 09.02.2011

Моя идея исходит из этого http://www.randombit.net/bitbashing/programming/integer_matrix_transpose_in_sse2.html

Я бы сегментировал один 8x8 на четыре 4x4, а затем проделал бы упомянутый трюк. наконец, поменяйте местами блок (0,1) и блок (1,0)

однако я до сих пор не понимаю, в чем трюк Пола Р. Пол, не могли бы вы дать мне еще несколько хитов?

person prgbenz    schedule 23.12.2010
comment
Ссылка мертвая, но оживить можно: http://wayback.archive.org/web/20100111104515/http://www.randombit.net/bitbashing/programming/integer_matrix_transpose_in_sse2.html - person Ekin; 02.06.2016