SSE: shuffle (permutevar) 4x32 целых числа - PullRequest
3 голосов
/ 08 мая 2019

У меня есть некоторый код, использующий встроенную AVX2 _mm256_permutevar8x32_epi32 aka vpermd для выбора целых чисел из входного вектора по индексу вектора. Теперь мне нужно то же самое, но для 4x32 вместо 8x32. _mm_permutevar_ps делает это с плавающей запятой, но я использую целые числа.

Одна идея - _mm_shuffle_epi32, но сначала мне нужно преобразовать мои значения индекса 4x32 в одно целое число, а именно:

imm[1:0] := idx[31:0]
imm[3:2] := idx[63:32]
imm[5:4] := idx[95:64]
imm[7:6] := idx[127:96]

Я не уверен, что это лучший способ сделать это, и более того, я не уверен, что это лучший способ продолжить. Я ищу наиболее эффективный метод на Бродвелле / Хасвелле, чтобы подражать «отсутствующим» _mm_permutevar_epi32(__m128i a, __m128i idx). Я бы предпочел использовать 128-битные инструкции, чем 256-битные, если это возможно (т.е. я не хочу расширять 128-битные входы, а затем сужать результат).

Ответы [ 2 ]

3 голосов
/ 08 мая 2019

Бесполезно генерировать немедленный во время выполнения, если вы не JIT новый код.Непосредственный - это байт, который буквально является частью кодирования инструкции машинного кода.Прекрасно, если у вас есть тасование с постоянной во время компиляции (после вставки + расширение шаблона), иначе забудьте о тех тасованиях, которые принимают операнд управления как целое число 1 .


До AVX тасование с переменным управлением only было SSSE3 pshufb.(_mm_shuffle_epi8).Это по-прежнему единственная 128-битная (или внутренняя) целочисленная команда перемешивания в AVX2, и я думаю, что AVX512.

AVX1 добавил некоторые 32-битные в строкепеременные тасовки, например vpermilps (_mm_permutevar_ps).AVX2 добавил целое число пересечения полосы и тасование FP, но как ни странно, нет 128-битной версии vpermd.Возможно, потому что у микроархитектур Intel нет штрафов за использование преобразований FP в целочисленных данных.(Что верно для семьи Сэндибридж, я просто не знаю, было ли это частью обоснования дизайна ISA).Но вы могли бы подумать, что они добавили бы __m128i встроенных функций для vpermilps, если бы это было то, что вы «должны» делать.Или, может быть, разработчики компиляторов / встроенных программ не согласились с людьми из набора инструкций asm?


Если у вас есть переменный вектор времени выполнения из 32-битных индексов и вы хотите сделатьв случайном порядке с 32-битной гранулярностью, безусловно, лучше всего использовать AVX _mm_permutevar_ps.

_mm_castps_si128( _mm_permutevar_ps (_mm_castsi128_ps(a), idx) )

По крайней мере, в Intel это даже не представитлюбая дополнительная задержка обхода при использовании между целочисленными инструкциями, такими как paddd;т. е. FP shuffles специально (не смешивает) не взимают штраф за использование целочисленных данных в процессорах семейства Sandybridge .

Если есть какие-либо штрафы для AMD Bulldozer или Ryzen,это незначительно и определенно дешевле, чем стоимость вычисления вектора управления тасованием для (v)pshufb.

Использование vpermd ymm и игнорирование старших 128 битов ввода и вывода (т. е. с использованием встроенных преобразований) будет намного медленнее на AMD (потому что его 128-битная SIMD-конструкция должна разделять 256-битные тасовки с пересечением полос на несколько мопов), а также хуже на Intel, где она делает задержку 3c вместо 1 цикла.


@ Ответ Ивилла показывает способ расчета вектора управления байтовыми индексами для перемешивания для pshufb из вектора 4x32-битных индексов двойного слова.Но он использует SSE4.1 pmulld, который составляет 2 мопа на большинстве процессоров, и может легко стать более узким местом, чем тасование.(См. Обсуждение в комментариях под этим ответом.) Особенно на более старых процессорах без AVX, некоторые из которых могут делать 2 pshufb за такт в отличие от современного Intel (в Haswell и более поздних версиях имеется только 1 порт случайного воспроизведения и легко узкое место в случайном порядке. IceLake добавит еще одинпорт shuffle, в соответствии с презентацией Intel Sunny Cove.)

Если вам нужно написать версию SSSE3 или SSE4.1, вероятно, все же лучше использовать только SSSE3 и использовать pshufb плюс сдвиг влеводублировать байт внутри dword перед ORing в 0,1,2,3 в младшие биты, а не pmulld.SSE4.1 pmulld имеет несколько мопов и даже хуже, чем pshufb на некоторых процессорах с медленным pshufb.(Вы можете вообще не выиграть от векторизации на процессорах только с SSSE3, а не с SSE4.1, то есть с Core2 первого поколения, потому что он имеет медленный выпуск pshufb.)

На Core2 второго поколения, иGoldmont, pshufb - инструкция с одним циклом с задержкой в ​​1 цикл.На Silvermont и Core 2 первого поколения это не так хорошо.Но в целом я бы порекомендовал pshufb + pslld + por, чтобы вычислить управляющий вектор для другого pshufb , если AVX недоступен .

Дополнительный случайный порядокподготовиться к перемешиванию гораздо хуже, чем просто использовать vpermilps на любом процессоре, поддерживающем AVX.


Сноска 1 :

Вам придетсяиспользуйте switch или что-то еще, чтобы выбрать путь к коду с правильным целым числом времени компиляции, и это ужасно;Только учтите, что если у вас нет даже SSSE3.Это может быть хуже, чем скаляр, если ветвь таблицы переходов не предсказывает идеально.

3 голосов
/ 08 мая 2019

Хотя Питер Кордес прав, говоря, что инструкция AVX vpermilps и ее собственный _mm_permutevar_ps(), вероятно, справятся с этой задачей, если вы работаете на машинах старше Sandy Bridge, вариант SSE4.1, использующий pshufb тоже неплохо работает.

AVX вариант

Кредиты @ PeterCordes

#include <stdio.h>
#include <immintrin.h>


__m128i vperm(__m128i a, __m128i idx){
    return _mm_castps_si128(_mm_permutevar_ps(_mm_castsi128_ps(a), idx));
}


int main(int argc, char* argv[]){
    __m128i a   = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
    __m128i idx = _mm_set_epi32(1,0,3,2);
    __m128i shu = vperm(a, idx);
    printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
                                    ((unsigned*)(&shu))[2],
                                    ((unsigned*)(&shu))[1],
                                    ((unsigned*)(&shu))[0]);
    return 0;
}

вариант SSE4.1

#include <stdio.h>
#include <immintrin.h>


__m128i vperm(__m128i a, __m128i idx){
    idx = _mm_and_si128  (idx, _mm_set1_epi32(0x00000003));
    idx = _mm_mullo_epi32(idx, _mm_set1_epi32(0x04040404));
    idx = _mm_or_si128   (idx, _mm_set1_epi32(0x03020100));
    return _mm_shuffle_epi8(a, idx);
}


int main(int argc, char* argv[]){
    __m128i a   = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
    __m128i idx = _mm_set_epi32(1,0,3,2);
    __m128i shu = vperm(a, idx);
    printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
                                    ((unsigned*)(&shu))[2],
                                    ((unsigned*)(&shu))[1],
                                    ((unsigned*)(&shu))[0]);
    return 0;
}

Это сводится к четкой

0000000000400550 <vperm>:
  400550:       c5 f1 db 0d b8 00 00 00         vpand  0xb8(%rip),%xmm1,%xmm1        # 400610 <_IO_stdin_used+0x20>
  400558:       c4 e2 71 40 0d bf 00 00 00      vpmulld 0xbf(%rip),%xmm1,%xmm1        # 400620 <_IO_stdin_used+0x30>
  400561:       c5 f1 eb 0d c7 00 00 00         vpor   0xc7(%rip),%xmm1,%xmm1        # 400630 <_IO_stdin_used+0x40>
  400569:       c4 e2 79 00 c1                  vpshufb %xmm1,%xmm0,%xmm0
  40056e:       c3                              retq

И-маскирование необязательно, если вы можете гарантировать, что управляющими индексами всегда будут 32-битные целые числа 0, 1, 2 или 3.

...