Как выполнить инверсию _mm256_movemask_epi8 (VPMOVMSKB)?
Внутренний:
int mask = _mm256_movemask_epi8(__m256i s1)
создает маску с ее 32
битами, соответствующими самому значащему биту каждого байта s1
. После манипулирования маской с использованием битовых операций (например, BMI2
) я хотел бы выполнить инверсию _mm256_movemask_epi8
, т. __m256i
вектор __m256i
с самым значительным битом каждого байта, содержащим соответствующий бит uint32_t mask
.
Каков наилучший способ сделать это?
- Что делает NOPL в системе x86?
- Какова цель инструкции «ПАУЗА» в x86?
- Заставить программу на x86?
- Что означает `dword ptr`?
- Как написать привет мир в ассемблере под Windows?
Изменить: мне нужно выполнить инверсию, потому что встроенный _mm256_blendv_epi8
принимает только __m256i
типа __m256i
вместо uint32_t
. Таким образом, в полученной маске __m256i
я могу игнорировать биты, отличные от MSB каждого байта.
- Assembly x86 Date to Number - разбиение строки на более мелкие разделы
- Разница в производительности между MSVC и GCC для высоко оптимизированного матричного кода
- Почему вы не можете установить указатель инструкции напрямую?
- Почему назначение целых чисел на естественно выровненной переменной атома на x86?
- Как точно работает инструкция x86 LOOP?
- Получение максимального значения в векторе __m128i с SSE?
- Наблюдение за устаревшей инструкцией по x86 с самомодифицируемым кодом
- Сколько байтов вводит push-команду в стек, если я не укажу размер операнда?
Вот альтернатива LUT или pdep
инструкциям, которые могут быть более эффективными:
- Скопируйте 32-разрядную маску на оба байта некоторых регистров
ymm
и на байты 16..19 того же регистра. Вы можете использовать временный массив и_mm256_load_si256
. Или вы можете перенести одиночную копию 32-разрядной маски на низкие байты некоторого регистраymm
, затем транслировать ее с помощьюVPBROADCASTD (_mm_broadcastd_epi32)
или других инструкций по широковещанию / перетасовке. - Переупорядочивайте байты регистра так, чтобы низкие 8 байтов (каждый) содержали 8 бит вашей маски, следующие 8 байтов – следующие 8 бит и т. Д. Это можно было бы сделать с помощью
VPSHUFB (_mm256_shuffle_epi8)
с регистром управления, содержащим «0» с низким значением 8 байты, ‘1’ в следующих 8 байтах и т. д. - Выберите правильный бит для каждого байта с
VPOR (_mm256_or_si256)
илиVPAND (_mm256_and_si256)
. - Установите MSB соответствующих байтов с
VPCMPEQB (_mm256_cmpeq_epi8)
. Сравните каждый байт с0xFF
. Если вы хотите, чтобы каждый бит маски переключился, используйтеVPAND
на предыдущем шаге и сравните с нолем.
Дополнительная гибкость этого подхода заключается в том, что вы можете выбрать другой регистр управления для шага №2 и другую маску для шага № 3 для перетасовки битов вашей битовой маски (например, вы можете скопировать эту маску в регистр ymm
в обратном порядке).
Я реализовал три подхода на машине Хасуэлла. Подход Евгения Клюева является самым быстрым (1,07 с), за ним следуют Джейсон Р (1,97 с) и Пол Р (2,44 с). Код ниже был скомпилирован с флагами -march = core-avx2 -O3.
#include #include //t_icc = 1.07 s //t_g++ = 1.09 s __m256i get_mask3(const uint32_t mask) { __m256i vmask(_mm256_set1_epi32(mask)); const __m256i shuffle(_mm256_setr_epi64x(0x0000000000000000, 0x0101010101010101, 0x0202020202020202, 0x0303030303030303)); vmask = _mm256_shuffle_epi8(vmask, shuffle); const __m256i bit_mask(_mm256_set1_epi64x(0x7fbfdfeff7fbfdfe)); vmask = _mm256_or_si256(vmask, bit_mask); return _mm256_cmpeq_epi8(vmask, _mm256_set1_epi64x(-1)); } //t_icc = 1.97 s //t_g++ = 1.97 s __m256i get_mask2(const uint32_t mask) { __m256i vmask(_mm256_set1_epi32(mask)); const __m256i shift(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)); vmask = _mm256_sllv_epi32(vmask, shift); const __m256i shuffle(_mm256_setr_epi64x(0x0105090d0004080c, 0x03070b0f02060a0e, 0x0105090d0004080c, 0x03070b0f02060a0e)); vmask = _mm256_shuffle_epi8(vmask, shuffle); const __m256i perm(_mm256_setr_epi64x(0x0000000000000004, 0x0000000100000005, 0x0000000200000006, 0x0000000300000007)); return _mm256_permutevar8x32_epi32(vmask, perm); } //t_icc = 2.44 s //t_g++ = 2.45 s __m256i get_mask1(uint32_t mask) { const uint64_t pmask = 0x8080808080808080ULL; // bit unpacking mask for PDEP uint64_t amask0, amask1, amask2, amask3; amask0 = _pdep_u64(mask, pmask); mask >>= 8; amask1 = _pdep_u64(mask, pmask); mask >>= 8; amask2 = _pdep_u64(mask, pmask); mask >>= 8; amask3 = _pdep_u64(mask, pmask); return _mm256_set_epi64x(amask3, amask2, amask1, amask0); } int main() { __m256i mask; boost::posix_time::ptime start( boost::posix_time::microsec_clock::universal_time()); for(unsigned i(0); i != 1000000000; ++i) { mask = _mm256_xor_si256(mask, get_mask3(i)); } boost::posix_time::ptime end( boost::posix_time::microsec_clock::universal_time()); std::cout << "duration:" << (end-start) << " mask:" << _mm256_movemask_epi8(mask) << std::endl; return 0; }
Вот еще одна реализация, которая может работать на AVX2, поскольку у вас есть этот тег на ваш вопрос (он непроверен, так как у меня нет машины Haswell). Это похоже на ответ Евгения Клюева, но может потребоваться меньше инструкций. Однако для этого требуются две постоянные маски __m256i
. Если вы делаете это много раз в цикле, то накладные расходы на настройку этих констант один раз досрочно могут быть незначительными.
-
Возьмите 32-битную маску и
_mm_broadcastd_epi32()
ее всем 8 слотам регистраymm
используя_mm_broadcastd_epi32()
. -
Создайте
__m256i
содержащий 8 32-битных целых чисел со значениями[0, 1, 2, 3, 4, 5, 6, 7]
(от наименее значимого до наиболее значимого элемента). -
Используйте эту постоянную маску для поворота каждого из 32-битных целых чисел в вашем регистре
ymm
оставшихся на другую величину, используя_mm256_sllv_epi32()
. -
Теперь, если мы рассмотрим регистр
ymm
как 8-битные целые числа и посмотрим на их MSB, тогда регистр теперь содержит MSB для индексов байт[7, 15, 23, 31, 6, 14, 22, 30, 5, 13, 21, 29, 4, 12, 20, 28, 3, 11, 19, 27, 2, 10, 18, 26, 1, 9, 17, 25, 0, 8, 16, 24]
значимый для наиболее значимого элемента). -
Используйте бит-И против постоянной маски
[0x80, 0x80, 0x80, ...]
чтобы изолировать MSB от каждого байта. -
Используйте последовательность перетасовки и / или перестановки, чтобы вернуть элементы в том порядке, в котором вы хотите. К сожалению, для 8-битных целых чисел, как и для значений с плавающей запятой в AVX2, нет никакой перестановки.
Мой первоначальный подход к этому был похож на @Jason R, потому что так работают «нормальные» операции, но большинство из этих операций заботятся только о высоком бите – игнорируя все остальные биты. Как только я понял это, _mm*_maskz_broadcast*_epi*(mask,__m128i)
имела наибольший смысл. Вам нужно будет включить -mavx512vl и -mavx512bw (gcc)
Чтобы получить вектор с самым высоким битом каждого байта, установленным в соответствии с маской:
/* convert 16 bit mask to __m128i control byte mask */ _mm_maskz_broadcastb_epi8((__mmask16)mask,_mm_set1_epi32(~0)) /* convert 32 bit mask to __m256i control byte mask */ _mm256_maskz_broadcastb_epi8((__mmask32)mask,_mm_set1_epi32(~0)) /* convert 64 bit mask to __m512i control byte mask */ _mm512_maskz_broadcastb_epi8((__mmask64)mask,_mm_set1_epi32(~0))
Чтобы получить вектор с самым высоким битом каждого слова, установленным в соответствии с маской:
/* convert 8 bit mask to __m128i control word mask */ _mm_maskz_broadcastw_epi16((__mmask8)mask,_mm_set1_epi32(~0)) /* convert 16 bit mask to __m256i control word mask */ _mm256_maskz_broadcastw_epi16((__mmask16)mask,_mm_set1_epi32(~0)) /* convert 32 bit mask to __m512i control word mask */ _mm512_maskz_broadcastw_epi16((__mmask32)mask,_mm_set1_epi32(~0))
Чтобы получить вектор с самым высоким битом каждого двойного слова, установленным в соответствии с маской:
/* convert 8 bit mask to __m256i control mask */ _mm256_maskz_broadcastd_epi32((__mmask8)mask,_mm_set1_epi32(~0)) /* convert 16 bit mask to __m512i control mask */ _mm512_maskz_broadcastd_epi32((__mmask16)mask,_mm_set1_epi32(~0))
Чтобы получить вектор с самым высоким битом каждого квадратного слова, установленным в соответствии с маской:
/* convert 8 bit mask to __m512i control mask */ _mm512_maskz_broadcastq_epi64((__mmask8)mask,_mm_set1_epi32(~0))
Единственное, что связано с этим вопросом, это: _mm256_maskz_broadcastb_epi8((__mmask32)mask,_mm_set1_epi32(~0))
но я включаю другие ссылки для сравнения / сравнения.
Обратите внимание, что каждый байт / слово / … будет либо всеми, либо всеми нулями в соответствии с маской (а не только самым высоким битом). Это также может быть полезно для выполнения векторизованных операций с битами (например, с другим вектором, например, для удаления ненужных байтов / слов).
Другое примечание: каждый _mm_set1_epi32(~0)
может / должен быть преобразован в константу (либо вручную, либо компилятором), поэтому он должен скомпилировать только одну довольно быструю операцию, хотя она может быть немного быстрее в тестировании, чем в реальной жизни, поскольку константа, вероятно, останется в регистре. Затем они преобразуются в команды VPMOVM2 {b, w, d, q}
Изменить: если ваш компилятор не поддерживает AVX512, встроенная версия сборки должна выглядеть так:
inline __m256i dmask2epi8(__mmask32 mask){ __m256i ret; __asm("vpmovm2b %1, %0":"=x"(ret):"k"(mask):); return ret; }
Другие инструкции похожи.
Единственный разумно эффективный способ, с которым я могу думать, – с 8-битным LUT: выполнить поиск по 4 x 8 бит, а затем загрузить результаты в вектор, например
static const uint64_t LUT[256] = { 0x0000000000000000ULL, ... 0xffffffffffffffffULL }; uint64_t amask[4] __attribute__ ((aligned(32))); uint32_t mask; __m256i vmask; amask[0] = LUT[mask & 0xff]; amask[1] = LUT[(mask >> 8) & 0xff]; amask[2] = LUT[(mask >> 16) & 0xff]; amask[3] = LUT[mask >> 24]; vmask = _mm256_load_si256((__m256i *)amask);
В качестве альтернативы вы можете использовать регистры вместо временного массива и посмотреть, может ли ваш компилятор сделать что-то более эффективное, что не связано с переходом через память:
static const uint64_t LUT[256] = { 0x0000000000000000ULL, ... 0xffffffffffffffffULL }; uint64_t amask0, amask1, amask2, amask3; uint32_t mask; __m256i vmask; amask0 = LUT[mask & 0xff]; amask1 = LUT[(mask >> 8) & 0xff]; amask2 = LUT[(mask >> 16) & 0xff]; amask3 = LUT[mask >> 24]; vmask = _mm256_set_epi64x(amask3, amask2, amask1, amask0);
Последующая мысль: интересной задачей может быть использование, например, инструкций Haswell BMI, чтобы выполнить эквивалент 8 -> 64-битной операции LUT и тем самым избавиться от LUT. Похоже, вы можете использовать PDEP
для этого, например
const uint64_t pmask = 0x8080808080808080ULL; // bit unpacking mask for PDEP uint64_t amask0, amask1, amask2, amask3; uint32_t mask; __m256i vmask; amask0 = _pdep_u64(mask, pmask); mask >>= 8; amask1 = _pdep_u64(mask, pmask); mask >>= 8; amask2 = _pdep_u64(mask, pmask); mask >>= 8; amask3 = _pdep_u64(mask, pmask); vmask = _mm256_set_epi64x(amask3, amask2, amask1, amask0);