Тасование по маске с Intel AVX
Я новичок в программировании AVX. У меня есть регистр, который необходимо перетасовать, точнее, я хочу переместить несколько байтов из 256-битного регистра R1 в пустой регистр R2. Я хочу определить маску, которая сообщает операции тасования, какой байт из старого регистра R1 должен быть скопирован в какое место в новом регистре.
Маска должна выглядеть следующим образом (Src:Byte Pos в R1, Target:Byte Pos в R2):
{(0,0),(1,1),(1,4),(2,5),...}
Это означает, что несколько байтов копируются дважды.
Я не уверен на 100%, какую функцию мне следует использовать для этого. Я попробовал немного с этими двумя функциями AVX, вторая просто использует 2 полосы.
__m256 _mm256_permute_ps (__m256 a, int imm8)
__m256 _mm256_shuffle_ps (__m256 a, __m256 b, const int imm8)
Я полностью запутался в маске Shuffle в imm8 и в том, как создать ее так, чтобы она работала, как описано выше.
Я посмотрел на этих слайдах(стр. 26), где описано _MM_SHUFFLE, но я не нашел решения с этим для моей проблемы.
Есть ли уроки, как создать такую маску? Или такие примеры функций для двух методов, чтобы глубже понять их?
Заранее спасибо за подсказки
1 ответ
TL:DR: вам, вероятно, понадобится несколько тасов для обработки пересечения линий, или если ваш паттерн продолжается точно так же, как вы можете использовать _mm256_cvtepu16_epi32
(vpmovzxwd
) а потом _mm256_blend_epi16
,
Для тасов x86 (как я думаю, как и большинства наборов инструкций SIMD) позиция назначения неявна. Константа случайного управления просто имеет исходные индексы в порядке назначения, будь то imm8
который компилируется + собирается прямо в инструкции asm или это вектор с индексом в каждом элементе.
Каждая позиция назначения читает ровно одну исходную позицию, но одну и ту же исходную позицию можно прочитать более одного раза. Каждый целевой элемент получает значение из источника случайного выбора.
См. Преобразование _mm_shuffle_epi32 в выражение C для перестановки? для простой версии C dst = _mm_shuffle_epi32(src, _MM_SHUFFLE(d,c,b,a))
, показывающий, как используется управляющий байт.
(За pshufb
/ _mm_shuffle_epi8
элемент с установленным старшим битом обнуляет эту позицию назначения вместо чтения какого-либо исходного элемента, но другие тасовки x86 игнорируют все старшие биты в векторах управления тасованием.)
Без маскирования слиянием AVX512 не может быть тасовок, которые бы смешивались с пунктом назначения. Есть некоторые тасовки из двух источников, такие как _mm256_shuffle_ps
(vshufps
), который может перемешивать элементы из двух источников для получения одного вектора результатов. Если вы хотите оставить некоторые элементы назначения неписанными, вам, вероятно, придется перемешать, а затем смешать, например, с _mm256_blendv_epi8
или если вы можете использовать смесь с 16-битной гранулярностью, вы можете использовать более эффективную немедленную смесь _mm256_blend_epi16
или даже лучше _mm256_blend_epi32
(AVX2 vpblendd
так же дешево, как _mm256_and_si256
на процессорах Intel, и это лучший выбор, если вам вообще нужно смешивать, если он может выполнить свою работу; см. http://agner.org/optimize/)
Для вашей проблемы (без AVX512VBMI vpermb
в Cannonlake), вы не можете перетасовать отдельные байты из 16-ти младших "дорожек" в верхние 16 "дорожек" __m256i
вектор с одной операцией.
Перестановки AVX не похожи на полную 256-битную SIMD, они больше похожи на две 128-битные операции параллельно. Единственное исключение - некоторые перестановки пересекающих полосы AVX2 с 32-битной гранулярностью или больше, например vpermd
(_mm256_permutevar8x32_epi32
). А также версии AVX2 pmovzx
/ pmovsx
например, pmovzxbq
расширяет ли ноль младшие 4 байта регистра XMM на 4 qwords регистра YMM, а не младшие 2 байта каждой половины регистра YMM. Это делает его намного более полезным с операндом источника памяти.
Но в любом случае, версия AVX2 pshufb
(_mm256_shuffle_epi8
) выполняет две отдельные перестановки 16x16 байтов в двух дорожках 256-битного вектора.
Вы, вероятно, захотите что-то вроде этого:
// Intrinsics have different types for integer, float and double vectors
// the asm uses the same registers either way
__m256i shuffle_and_blend(__m256i dst, __m256i src)
{
// setr takes element in low to high order, like a C array init
// unlike the standard Intel notation where high element is first
const __m256i shuffle_control = _mm256_setr_epi8(
0, 1, -1, -1, 1, 2, ...);
// {(0,0), (1,1), (zero) (1,4), (2,5),...} in your src,dst notation
// Use -1 or 0x80 or anything with the high bit set
// for positions you want to leave unmodified in dst
// blendv uses the high bit as a blend control, so the same vector can do double duty
// maybe need some lane-crossing stuff depending on the pattern of your shuffle.
__m256i shuffled = _mm256_shuffle_epi8(src, shuffle_control);
// or if the pattern continues, and you're just leaving 2 bytes between every 2-byte group:
shuffled = _mm256_cvtepu16_epi32(src); // if src is a __m128i
__m256i blended = _mm256_blendv_epi8(shuffled, dst, shuffle_control);
// blend dst elements we want to keep into the shuffled src result.
return blended;
}
Обратите внимание, что pshufb
нумерация начинается с 0 для 2-х 16-ти байтов. Две половинки __m256i
могут отличаться, но они не могут читать элементы из другой половины. Если вам нужны позиции в верхнем ряду, чтобы получить байты из нижнего ряда, вам потребуется больше перемешивания + смешивания (например, включая vinserti128
или же vperm2i128
или, может быть, vpermd
пересекая полосу (dword shuffle), чтобы собрать все необходимые байты в одну 16-байтовую группу в некотором порядке.
(На самом деле _mm256_shuffle_epi8
(PSHUFB) игнорирует биты 4..6 в индексе тасования, поэтому запись 17
такой же как 1
, но очень вводит в заблуждение. Это эффективно делает %16
До тех пор, пока старший бит не установлен. Если старший бит установлен в векторе случайного управления, он обнуляет этот элемент. Нам не нужна эта функциональность здесь; _mm256_blendv_epi8
не заботится о старом значении заменяемого элемента)
В любом случае, этот простой пример с 2 инструкциями работает только в том случае, если шаблон не продолжается. Если вам нужна помощь в разработке ваших настоящих перемешиваний, вам придется задать более конкретный вопрос.
И кстати, я заметил, что ваш шаблон смешивания использовал 2 новых байта, а затем 2 пропущенных 2. Если это продолжается, вы можете использовать vpblendw
_mm256_blend_epi16
вместо blendv
, потому что эта инструкция выполняется только 1 моп вместо 2 на процессорах Intel. Это также позволит вам использовать AVX512BW vpermw
16-разрядный тасовщик, доступный в современных процессорах Skylake-AVX512, вместо, возможно, даже более медленного AVX512VBMI vpermb
,
Или на самом деле, возможно, это позволит вам использовать vpmovzxwd
(_mm256_cvtepu16_epi32
) для расширения нуля 16-битных элементов до 32-битных, в качестве перестановки пересекающих полосы. Затем смешайте с dst
,