Чтобы очистить все биты, вы часто видите исключение или как в XOR eax, eax
. Есть ли такой трюк и для противоположного?
Все, что я могу представить, это инвертировать нули с дополнительной инструкцией.
Чтобы очистить все биты, вы часто видите исключение или как в XOR eax, eax
. Есть ли такой трюк и для противоположного?
Все, что я могу представить, это инвертировать нули с дополнительной инструкцией.
Для большинства архитектур с инструкциями фиксированной ширины ответом, вероятно, будет скучная инструкция mov
немедленного расширения или инвертирования знака или пары mov lo/high. например на ARM, mvn r0, #0
(не двигаться). Смотрите вывод gcc asm для x86, ARM, ARM64 и MIPS, в проводнике компилятора Godbolt. IDK что-нибудь о zseries asm или машинном коде.
В ARM eor r0,r0,r0
значительно хуже, чем mov -immediate. Это зависит от старого значения, без специальной обработки. Правила упорядочения зависимостей в памяти не позволяют ARM uarch использовать его в специальном регистре, даже если они этого хотят. То же самое относится к большинству других RISC ISA со слабо упорядоченной памятью, но для которых не требуются барьеры для memory_order_consume
( в терминологии С++ 11).
x86 xor-zeroing является особенным из-за его набора команд переменной длины.
Исторически 8086 xor ax,ax
был быстрым напрямую, потому что он был маленьким. Поскольку эта идиома стала широко использоваться (и обнуление встречается гораздо чаще, чем все), разработчики процессоров оказали ей особую поддержку, и теперь xor eax,eax
быстрее, чем mov eax,0
в семействе Intel Sandybridge и некоторых других процессорах, даже без учета прямые и косвенные эффекты размера кода. См. Как лучше всего установить регистр в ноль в сборке x86: xor, mov или и?, чтобы узнать о преимуществах микроархитектуры, которые мне удалось выкопать.
Если бы у x86 был набор инструкций фиксированной ширины, интересно, получил бы mov reg, 0
такой же особый режим, как и при обнулении xor? Возможно, потому что нарушение зависимости перед записью low8 или low16 важно.
Стандартные параметры для лучшей производительности:
mov eax, -1
: 5 байтов, используя кодировку mov r32, imm32
. (К сожалению, расширение TG410 отсутствует). Отличная производительность на всех процессорах. 6 байтов для r8-r15 (префикс REX).mov rax, -1
: 7 байтов, используя кодировку mov r/m64, sign-extended-imm32
. (Не версия REX.W = 1 версии eax
. Это будет 10-байтовый mov r64, imm64
). Отличная производительность на всех процессорах.Странные варианты, которые сохраняют некоторый размер кода, обычно за счет производительности:
xor eax,eax
/dec rax
(или not rax
): 5 байтов (4 для 32-разрядных eax
). Недостаток: два мопа для внешнего интерфейса. Все еще только одно неиспользуемое UOP домена для планировщика/исполнительных модулей на недавнем Intel, где xor-zeroing обрабатывается во внешнем интерфейсе. mov
-immediate всегда нужен исполнительный блок. (Но целочисленная пропускная способность ALU редко является узким местом для инструкций, которые могут использовать любой порт; проблема в дополнительном входном давлении)xor ecx,ecx
/lea eax, [rcx-1]
Всего 5 байтов для 2 констант (6 байтов для rax
): оставляет отдельный обнуленный регистр. Если вы уже хотите обнулить регистр, то у этого недостатка почти нет. lea
может работать на меньшем количестве портов, чем mov r,i
, на большинстве процессоров, но, поскольку это начало новой цепочки зависимостей, центральный процессор может запустить его в любом цикле резервного порта выполнения после того, как он выдаст ошибку.
Тот же трюк работает для любых двух соседних констант, если вы делаете первый с mov reg, imm32
, а второй с lea r32, [base + disp8]
. disp8 имеет диапазон от -128 до +127, в противном случае вам нужен disp32
.
or eax, -1
: 3 байта (4 для rax
) с использованием кодировки or r/m32, sign-extended-imm8
. Недостаток: ложная зависимость от старого значения регистра.
push -1
/pop rax
: 3 байта. Медленно, но мало. Рекомендуется только для эксплойтов/код-гольфа. Работает для любого sign-extended-imm8, в отличие от большинства других.
МИНУСЫ:
rax
не будет готов к ~ 5 циклам, например, после этого на Skylake.rsp
напрямую, он выполнит синхронизацию стека. (например, для add rsp, 28
или для mov eax, [rsp+8]
).Установка векторных регистров на единичные с помощью pcmpeqd xmm0,xmm0
имеет особый случай на большинстве процессоров как нарушение зависимости (не Silvermont/KNL), но все еще нуждается в исполнительном модуле, чтобы фактически записать их. pcmpeqb/w/d/q
все работает, но q
медленнее на некоторых процессорах.
Для AVX2, ymm
эквивалент vpcmpeqd ymm0, ymm0, ymm0
также является лучшим выбором.
Для AVX без AVX2 выбор менее очевиден: не существует единственного очевидного лучшего подхода. Компиляторы используют различные стратегии: gcc предпочитает загружать 32-байтовую константу с vmovdqa
, в то время как более старый clang использует 128-битный vpcmpeqd
, за которым следует перекрестная линия vinsertf128
, чтобы заполнить верхнюю половину. Более новый кланг использует vxorps
для обнуления регистра, а затем vcmptrueps
для его заполнения. Это моральный эквивалент подхода vpcmpeqd
, но vxorps
необходим для устранения зависимости от предыдущей версии регистра, а задержка vcmptrueps
равна 3. Это разумный выбор по умолчанию.
Выполнение vbroadcastss
из 32-битного значения, вероятно, строго лучше, чем подход с загрузкой, но сложно заставить компиляторы генерировать это.
Лучший подход, вероятно, зависит от окружающего кода.
Самый быстрый способ установить значение __m256 для всех ОДИН битов
Сравнения AVX512 доступны только с регистром маски (например, k0
) в качестве места назначения, поэтому в настоящее время компиляторы используют vpternlogd zmm0,zmm0,zmm0, 0xff
в качестве идиомы "все единицы" 512b. (0xff делает каждый элемент таблицы истинности с 3 входами 1
). Это не является специальным случаем, как нарушение зависимости на KNL или SKL, но имеет пропускную способность 2 на тактовую частоту на Skylake-AVX512. Это лучше, чем использование более узких AVX-устройств, разбивающих зависимости, и их трансляция или перетасовка.
Если вам нужно заново сгенерировать все единицы внутри цикла, очевидно, что наиболее эффективный способ - использовать vmov*
для копирования регистра всех единиц. Это даже не использует исполнительный модуль на современных процессорах (но все же требует пропускной способности внешнего интерфейса). Но если у вас нет векторных регистров, загрузка константы или [v]pcmpeq[b/w/d]
- хороший выбор.
Для AVX512 стоит попробовать VPMOVM2D zmm0, k0
или, может быть, VPBROADCASTD zmm0, eax
. Каждый из них имеет пропускную способность только 1с, но они должны нарушать зависимости от старого значения zmm0 (в отличие от vpternlogd
). Им требуется маска или регистр целых чисел, который вы инициализировали вне цикла с помощью kxnorw k1,k0,k0
или mov eax, -1
.
Для регистров маски AVX512, kxnorw k1,k0,k0
работает, но это не нарушение зависимости от текущих процессоров. Руководство по оптимизации Intel предлагает использовать его для генерации единиц перед командой сбора, но рекомендует избегать использования того же входного регистра, что и для вывода. Это позволяет избежать зависимости, независимой от других сборок, от предыдущей в цикле. Поскольку k0
часто не используется, его обычно удобно читать.
Я думаю, что vpcmpeqd k1, zmm0,zmm0
будет работать, но он, вероятно, не имеет специального случая как идиома k0 = 1 без зависимости от zmm0. (Чтобы установить все 64 бита вместо 16 младших, используйте AVX512BW vpcmpeqb
)
На Skylake-AVX512 инструкции k
, которые работают с регистрами маски , выполняются только на одном порту, даже на таких простых, как kandw
. (Также обратите внимание, что Skylake-AVX512 не будет запускать векторные мопы на порте 1, когда в канале есть какие-либо 512-битные операции, поэтому пропускная способность исполнительного модуля может стать настоящим узким местом.)
Нет kmov k0, imm
, только ходы из целого числа или памяти. Вероятно, нет инструкций k
, в которых то же самое определяется как специальное, поэтому оборудование на этапе выпуска/переименования не ищет его для регистров k
.
Петр уже дал идеальный ответ. Я просто хочу отметить, что это также зависит от контекста.
Я на этот раз сделал sar r64, 63
числа, которое я знаю, будет отрицательным в определенном случае, и если нет, мне не нужно будет устанавливать все биты. Преимущество sar
состоит в том, что он устанавливает некоторые интересные флаги, хотя на самом деле декодирует 63
, тогда я мог бы также сделать mov r64, -1
. Я думаю, что это были флаги, которые позволили мне сделать это в любом случае.
Итак, суть: контекст. Как вы знаете, вы обычно углубляетесь в язык ассемблера, потому что хотите обрабатывать дополнительные знания, а не компилятор. Может быть, в некоторых из ваших регистров, значение которых вам больше не нужно, хранится 1
(так логически true
), а затем просто neg
его. Может быть, где-то ранее в вашей программе вы сделали loop
, а затем (при условии, что он управляем) вы можете организовать использование своего регистра таким образом, чтобы все, чего не хватает, not rcx
.