const __m128i mask = _mm_set1_epi8(0x0F);
const __m128i vec_unpack_one = _mm_and_si128(vec, mask);
const __m128i vec_unpack_two = _mm_and_si128(_mm_srli_epi16(vec, 4), mask);
У меня есть набор из 32 полубайтов, хранящихся в vec. Я хочу распаковать его и сохранить каждый полубайт как байт, что и пытаются сделать 2-я и 3-я строки фрагмента кода. Однако я хочу сохранить знак полубайта и расширить его до байта.
Например, один из 8-битных элементов vec — 01111011.
В vec_unpack_one оно распаковывается в настоящее время как 00001011, тогда как в vec_unpack_two оно распаковывается как 00000111. Однако я хочу, чтобы распакованное значение в vec_unpacked_one было 11111011, иначе значения, используемые в последующей операции, будут отличаться от того, что было на самом деле задумано.
Текущее решение, которое я имел в виду, состоит в том, чтобы отделить старший бит полубайта от побитовых операций и выполнить какие-то маскируемые операции или операции, основанные на битах. Но есть ли способы добиться этого посредством прямого указания или более эффективными способами. Предложения приветствуются. Спасибо
Расширение знака полубайта можно выполнить с помощью pshufb
(_mm_shuffle_epi8
) в качестве таблицы поиска. Вам все равно необходимо замаскировать старшие биты, поскольку установка старшего бита в индексном байте обнуляет соответствующий вывод вместо индексации другого вектора.
Таким образом, вы все равно начнете с того же кода, что у вас есть (стандартный способ разделения полубайтов), и сделаетеv0 = _mm_shuffle_epi8(sign_extend_lut, v0)
и то же самое для v1
.
Вероятно, это ваш лучший выбор по сравнению с битхаком типа (x ^ m) - m
(2 инструкции на половину с использованием _mm_xor_si128
и _mm_sub_epi8
), где m
- это 1U << 3
или 8
(Знак расширения девятибитного числа в C / https://graphics.stanford .edu/~seander/bithacks.html#FixedSignExtend), для которого также необходимо заранее обнулить старшие биты.
Если ваш окружающий код не слишком перегружен перемешиванием, особенно если важны старые процессоры Intel (семейство Haswell и Skylake с пропускной способностью перемешивания всего 1/такт: https://uops.info). Тогда, возможно, рассмотрите битхак.
Мы можем выполнить XOR входных данных с помощью _mm_set1_epi8(0x88)
перед разделением полубайтов, так что эта версия битхака будет всего на один моп дороже, чем версия pshufb
вместо двух, хотя тогда для нее потребуются две разные векторные константы. (Спасибо @chtz).
Самый узкий сдвиг SIMD в x86 — 16-битный, поэтому арифметический сдвиг вправо, к сожалению, не поможет.
Если бы вы позже расширили до 16 или 32-бит, вы могли бы vpmovsxbw
или vpmovsxbd
, а затем арифметический сдвиг вправо. (_mm_cvtepi8_epi16
/ _mm_srai_epi16
или их _mm256
эквиваленты), по крайней мере, для верхних полубайтов.
Для битхака вы можете выполнить xor каждого байта с помощью
0x88
перед сдвигом + маскированием, что приведет к увеличению всего на один моп по сравнению с версиейpshufb
. А вычитание0x08
(или добавление0xf8
) в некоторых случаях может сопровождаться последующими инструкциями.