Установите все биты в регистре процессора на 1 эффективно
Чтобы очистить все биты, вы часто видите эксклюзив или как в XOR eax, eax
, Есть ли такая хитрость для противоположности?
Все, что я могу думать, это инвертировать нули с помощью дополнительной инструкции.
1 ответ
Для большинства архитектур с инструкциями фиксированной ширины ответ, вероятно, будет скучным mov
немедленного расширения знака или инверсии, или пары mov lo/high. например, на ARM, mvn r0, #0
(Движение-нет). Смотрите вывод gcc asm для x86, ARM, ARM64 и MIPS в проводнике компилятора Godbolt. ИДК что-нибудь про zseries asm или машинный код.
В ARM, eor r0,r0,r0
значительно хуже, чем мов-немедленный. Это зависит от старого значения, без особой обработки. Правила упорядочения зависимостей в памяти не допускают, чтобы ARM-uarch выделил его, даже если бы захотел. То же самое относится и к большинству других RISC ISA со слабо упорядоченной памятью, но для этого не нужны барьеры для memory_order_consume
(в терминологии C++11).
x86 xor-zeroing является особенным из-за его набора команд переменной длины. Исторически, 8086 xor ax,ax
было быстро, потому что он был маленьким. Поскольку идиома стала широко использоваться (и обнуление встречается гораздо чаще, чем все), разработчики ЦП оказали ей особую поддержку, и теперь xor eax,eax
быстрее чем mov eax,0
на семействе Intel Sandybridge и некоторых других процессорах, даже без учета прямых и косвенных эффектов размера кода. См. Каков наилучший способ установить регистр в ноль в сборке x86: xor, mov или и? за столько микро-архитектурных преимуществ, сколько мне удалось выкопать.
Если бы у x86 был набор инструкций фиксированной ширины, интересно, mov reg, 0
получил бы столько же особого отношения, как и при обнулении? Возможно, потому что нарушение зависимости перед записью low8 или low16 важно.
Стандартные опции для лучшей производительности:
mov eax, -1
: 5 байт, используяmov r32, imm32
кодирование. (Там нет расширения знакаmov r32, imm8
, к несчастью). Отличная производительность на всех процессорах. 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 обрабатывается во внешнем интерфейсе.mov
-посредственно всегда нужен исполнительный блок. (Но целочисленная пропускная способность 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, в отличие от большинства других.Недостатки:
- использует блоки сохранения и загрузки, а не ALU. (Возможно, преимущество в пропускной способности в редких случаях на семействе AMD Bulldozer, где есть только два целочисленных канала выполнения, но пропускная способность декодирования / выдачи / вывода выше, чем это. Но не пытайтесь сделать это без тестирования.)
- средство задержки / перезагрузки
rax
например, не будет готов к ~5 циклам после того, как это выполнится на Skylake. - (Intel): переводит стековый движок в режим, модифицированный rsp, так что в следующий раз вы прочитаете
rsp
непосредственно это займет синхронизацию стека. (например, дляadd rsp, 28
или дляmov eax, [rsp+8]
). - Магазин может пропустить кеш, вызывая дополнительный трафик памяти. (Возможно, если вы не касались стека внутри длинного цикла).
Векторы разные
Установка векторных регистров для всех с pcmpeqd xmm0,xmm0
Специально размещается на большинстве процессоров как нарушитель зависимостей (не Silvermont/KNL), но все еще нуждается в исполнительном модуле, чтобы фактически написать их. pcmpeqb/w/d/q
все работают, но q
медленнее на некоторых процессорах.
Версия AVX/AVX2 этого также является лучшим выбором там. Самый быстрый способ установить значение __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
это все, чего не хватает.