Как использовать биты в байте для установки слов в регистре ymm без AVX2? (Инверсия vmovmskps)
То, чего я пытаюсь достичь, основано на каждом бите в байте, установленном на все единицы в каждом dword в регистре ymm (или в ячейке памяти)
например
al = 0110 0001
ymm0 = 0x00000000 FFFFFFFF FFFFFFFF 00000000 00000000 00000000 00000000 FFFFFFFF
то есть обратная vmovmskps eax, ymm0
/ _mm256_movemask_ps
, превращая растровое изображение в векторную маску.
Я думаю, что есть несколько инструкций sse/avx, которые могут сделать это относительно просто, но я не смог решить это. Желательно совместим с песчаным мостиком, поэтому нет avx2.
2 ответа
Как описано в разделе: есть ли обратная инструкция к инструкции movemask в intel avx2? Вы можете разделить свое растровое изображение на два 4-битных блока для использования с LUT. Это будет работать довольно хорошо: vinsertf128
имеет 1 пропускную способность за такт на Sandybridge и один на 0,5 с на Haswell/Skylake.
Решение ALU с AVX1 может просто выполнить ту же самую работу дважды для верхних / нижних векторных половин (передать растровое изображение, замаскировать его, vpcmpeqd xmm
), затем vinsertf128
но это вроде отстой.
Вы можете рассмотреть возможность сделать версию AVX2 отдельной от вашей версии только для AVX1, используя vpbroadcastd ymm0, mem
/ vpand ymm0, mask
/ vpcmpeqd dst, ymm0, mask
потому что это очень эффективно, особенно если вы загружаете растровое изображение из памяти и можете прочитать целое слово для растрового изображения. (Для трансляции dword или qword не требуется ALU shuffle). mask
является set1_epi32(1<<7, 1<<6, 1<<5< ..., 1<<0)
, который вы можете загрузить с vpmovzxbd ymm, qword [constant]
поэтому для 8 элементов требуется всего 8 байтов памяти.
Если мы проявим творческий подход, мы можем использовать инструкции AVX1 FP, чтобы сделать то же самое. AVX1 имеет трансляцию меча (vbroadcastss ymm0, mem
) и логические значения (vandps
). Это даст битовые шаблоны, которые являются действительными числами с одинарной точностью, чтобы мы могли использовать vcmpeqps
, но они все ненормальны, если мы оставляем биты растрового изображения в нижней части элемента. Это может быть хорошо для Sandybridge: не может быть никакого штрафа за сравнение ненормальных. Но он сломается, если ваш код когда-либо будет работать с DAZ (denormals-are-zero), поэтому мы должны избегать этого.
Мы могли бы vpor
с помощью чего-либо, чтобы установить показатель степени до или после маскирования, или мы могли бы сдвинуть растровое изображение вверх в 8-разрядное поле экспоненты в формате IEEE с плавающей запятой. Если ваше растровое изображение начинается в целочисленном регистре, его смещение будет хорошо, потому что shl eax, 23
до movd
дешево. Но если это начинается в памяти, это значит отказаться от использования дешевого vbroadcastss
нагрузки. Или вы могли бы транслировать-загрузить в хмм, vpslld xmm0, xmm0, 23
/ vinsertf128 ymm0, xmm0, 1
, Но это все еще хуже, чем vbroadcastss
/ vorps
/ vandps
/ vcmpeqps
Так:
# untested
# pointer to bitmap in rdi
inverse_movemask:
vbroadcastss ymm0, [rdi]
vorps ymm0, ymm0, [set_exponent] ; or hoist this constant out with a broadcast-load
vmovaps ymm7, [bit_select] ; hoist this out of any loop, too
vandps ymm0, ymm0, ymm7
; ymm0 exponent = 2^0, mantissa = 0 or 1<<i where i = element number
vcmpeqps ymm0, ymm0, ymm7
ret
section .rodata
ALIGN 32
bit_select: dd 0x3f800000 + 1<<7, 0x3f800000 + 1<<6
dd 0x3f800000 + 1<<5, 0x3f800000 + 1<<4
dd 0x3f800000 + 1<<3, 0x3f800000 + 1<<2
dd 0x3f800000 + 1<<1, 0x3f800000 + 1<<0
set_exponent: times 8 dd 0x3f800000 ; 1.0f
; broadcast-load this instead of duplicating it in memory if you're hoisting it.
Вместо широковещательной загрузки set_exponent
вместо этого вы могли бы перемешать bit_select
: пока 0x3f800000
биты установлены, не имеет значения, если элемент 0 также устанавливает бит 3 или что-то, только не бит 0. Так vpermilps
или же vshufps
копировать-и-перемешать будет работать.
https://www.h-schmidt.net/FloatConverter/IEEE754.html - это полезное преобразование шестнадцатеричных битовых значений IEEE754 FP <-> в случае, если вы хотите проверить, какое значение представляет битовый паттерн FP.
vcmpeqps
имеет ту же задержку и пропускную способность, что и vaddps
на всех процессорах Intel. (Это не совпадение; они работают на одном и том же исполнительном блоке). Это означает задержку в 3 цикла на SnB-Broadwell и задержку в 4 цикла на Skylake. Но vpcmpeqd
только 1с латентность.
Таким образом, этот метод имеет хорошую пропускную способность (только на 1 моп больше, чем целое число AVX2, где vorps
не требуется), но худшая задержка на 3 цикла или 4 на Skylake.
Но не опасно ли сравнение чисел с плавающей запятой или плохая практика?
Сравнение для точного равенства может дать неожиданные результаты, когда один из входных данных сравнения является округленным результатом вычисления (например, выходной результат vaddps
или же vmulps
). Серия блогов Брюса Доусона по математике FP вообще и x86 в частности превосходна, особенно Сравнение чисел с плавающей запятой, выпуск 2012 года. Но в этом случае мы контролируем битовые шаблоны FP, а округления нет.
Не-NaN значения FP с одинаковым битовым шаблоном всегда будут сравниваться одинаково.
Значения FP с разными битовыми комбинациями всегда будут сравниваться как не равные, кроме -0.0
а также +0.0
(которые отличаются только знаковым битом) и денормализованными значениями в режиме DAZ. Последнее, почему мы используем vpor
; вы можете пропустить его, если знаете, что DAZ отключен, а ваше оборудование FP не требует помощи для сравнения ненормальных значений. (IIRC, Sandybridge этого не делает, и может даже добавлять / субнормальные без вспомогательной помощи. Когда на аппаратном обеспечении Intel требуются вспомогательные микрокоды, это обычно происходит при получении ненормального результата из обычных входных данных, но сравнение не дает результата FP.)
Предисловие: Я знаю, что это не соответствует (целым) требованиям вопроса, поэтому этот ответ не приемлем. Я просто публикую это для дальнейшего использования.
Существует новая инструкция AVX512(VL|BW) с именем VPMOVM2B, которая делает то, что вы хотите, ровно в одной инструкции:
VPMOVM2B ymm1, k1
Устанавливает каждый байт в YMM1 на все 1 или все 0 на основе значения соответствующего бита в k1.
Я не мог проверить это, но это должно быть то, что вы хотите.