Делать мне было нечего, поэтому я решил изучить AVX-512 и попробовать написать с его помощью что-нибудь на ассемблере. Я пытаюсь оптимизировать фрагмент кода, который обрабатывает большие объемы данных, используя инструкции AVX-512. Цель — максимизировать возможности векторных регистров и минимизировать количество циклов процессора.
Проблема вот в чем: я хочу использовать маскирование для обработки только части элементов в регистрах zmm0 и zmm1 в зависимости от определенного условия. Однако инструкции AVX-512 с масками (например, vaddps
) требуют маски в регистре k0-k7:
vmovups zmm0, [rsi] ; float[16] <= zmm0
vmovups zmm1, [rsi+64]
; some code here
vaddps zmm0, zmm0, zmm11
vmovups [rdi], zmm0
add rsi, 128 ; ptr => next[data]
add rdi, 64 ; ptr => next[data] ?to: write
При этом условие, по которому я хочу замаскировать данные, получается путем сравнения двух других регистров zmm.
Итак, вот вопрос:
Есть ли способ эффективно сгенерировать маску в регистре k на основе сравнения значений в регистрах zmm, а затем использовать ее для выборочной обработки данных с помощью инструкций AVX-512? А может быть, есть другой способ добиться желаемого результата с помощью AVX-512, не прибегая к маскам?
Я помню, что есть vpcmpd, который сравнивает значения векторных регистров, и якобы можно сделать что-то вроде k1 = zmm0 > zmm1 + k2 = zmm0 < zmm2
, но, честно говоря, я понятия не имею, насколько это может быть эффективно; Я попробовал, но из-за нехватки знаний отказался от этой идеи.
vpcmpd
это именно то, что вы хотите. Ну, возможно vpcmps
, поскольку остальная часть вашего кода ps
, но это не имеет значения. Вы можете использовать версию, имеющую входной регистр маски для формирования логического «и». Значения маски, которые были ложными после первого cmp, остаются ложными после второго, если они были замаскированы во втором cmp. Почему вы думаете, что это должно быть неэффективно?
Помните, что инструкция AVX занимает одинаковое количество времени независимо от того, работает ли она с каждым элементом или только с некоторыми из них. Таким образом, вы можете выполнить серию операций с полным регистром, а затем замаскировать ненужные значения в конце.
ТАСМ? Я думал, что Turbo Assembler поддерживает только 16- и 32-битные режимы и до сих пор не обновляется новыми функциями, такими как поддержка AVX-512! Я бы порекомендовал NASM для рукописного asm с использованием синтаксиса Intel, если только TASM вам действительно не подходит.
@Питер Кордес Ой, я виноват. Я имел в виду NASM, даже не заметил опечатку "tasm" :)
@Homer512 Спасибо. Кроме того, я действительно не думал, что это может быть заразно; Я просто усомнился в своих силах, потому что не знаю, как это реализовать на самом деле, даже если решение простое. Я так понимаю, ты имеешь в виду что-то в этом роде? vpcmpd k1, zmm0, zmm1, 1 ; greater
vpcmpd k2, zmm0, zmm2, 2 ; less
Тогда вам следует отредактировать свой вопрос, чтобы исправить его.
Для сравнения ФП вам нужен vcmpps
. felixcloutier.com/x86/cmpps . NASM должен поддерживать псевдонимы типа vcmpgtps
, которые подразумевают немедленное действие.
Я к тому, что вам не нужны две маски k1
и k2
. На самом деле выполнение логических операций над такими масками, как kandw
, происходит довольно медленно. Для этого вам понадобится только один. Примерно так: godbolt.org/z/Khss8MYfY
Подводя итог обсуждению комментариев: вы были правы, предположив, что семейство инструкций vcmp
, таких как vcmpps
, является подходящим способом сделать это. Инструкции AVX512 в маске обычно выполняются быстро. Когда это возможно, используйте инструкции маски обнуления, такие как vaddps zmm1{k1}{z}, zmm2, zmm3
, вместо инструкций по слиянию, таких как vaddps zmm1{k1}, zmm3, zmm0
, чтобы избежать зависимости от содержимого предыдущего регистра.
При использовании регистров маски следует учитывать, что некоторые инструкции по их вычислению выполняются довольно медленно. Например, kadd имеет задержку 4 на Intel согласно uops.info, в то время как kand имеет задержку только 1, но пропускная способность все равно только 1.
Однако зачастую таким образом можно избежать комбинирования масок. vcmp
сам принимает маску ввода. Выходная маска будет равна нулю там, где входная маска была нулевой. Это соединение И. Например, условие zmm1 < zmm2 && zmm2 < zmm3
можно записать как
vcmpps k1, zmm1, zmm2, 1 ; _CMP_LT_OS
vcmpps k1{k1}, zmm2, zmm3, 1
Мы не можем сформировать соединение ИЛИ таким образом, но мы все равно можем избежать использования двух регистров маски. Например, zmm1 < zmm2 || zmm2 < zmm3
то же самое, что ! (! (zmm1 < zmm2) && ! (zmm2 < zmm3))
согласно законам Де Моргана
vcmpps k1, zmm1, zmm2, 5 ; _CMP_NLT_US
vcmpps k1{k1}, zmm2, zmm3, 5
knotw k1, k1
С другой стороны, использование двух масок и их объединение через korw
устранит зависимость ввода от одной vcmp
к другой, потенциально увеличивая параллелизм на уровне инструкций.
Возможно, вы ищете VPMOVB2M/VPMOVW2M/VPMOVD2M/VPMOVQ2M Преобразование векторного регистра в маску