Все следующие инструкции делают то же самое: установить %eax
на ноль. Какой способ оптимален (требует наименьшего числа машинных циклов)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Все следующие инструкции делают то же самое: установить %eax
на ноль. Какой способ оптимален (требует наименьшего числа машинных циклов)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Ответы:
TL; DR Резюме : xor same, same
является лучшим выбором для всех процессоров . Никакой другой метод не имеет перед ним преимуществ, и он имеет хоть какое-то преимущество перед любым другим методом. Это официально рекомендовано Intel и AMD, а также тем, что делают компиляторы. В 64-битном режиме все еще используйте xor r32, r32
, потому что запись 32-битного регистра обнуляет верхние 32 . xor r64, r64
это пустая трата байта, потому что ему нужен префикс REX.
Еще хуже то, что Silvermont распознает только xor r32,r32
дескрипцию, а не 64-битный размер операнда. Таким образом, даже когда префикс REX все еще требуется, потому что вы обнуляете r8..r15, используйте xor r10d,r10d
, а неxor r10,r10
.
Примеры целочисленных GP:
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
Обнуление векторного регистра обычно лучше всего выполнять с помощью pxor xmm, xmm
. Обычно это то, что делает gcc (даже до использования с инструкциями FP).
xorps xmm, xmm
может иметь смысл. Это на один байт короче pxor
, но для этого xorps
требуется порт выполнения 5 на Intel Nehalem, при этом он pxor
может работать на любом порту (0/1/5). (Задержка задержки обхода 2c Nehalem между целым числом и FP обычно не имеет значения, поскольку выполнение вне очереди обычно может скрыть ее в начале новой цепочки зависимостей).
В микроархитектурах семейства SnB ни один из вариантов xor-zeroing даже не требует порта выполнения. На AMD и Intel до Nehalem P6 / Core2 xorps
иpxor
обрабатываются одинаково (как векторные целочисленные инструкции).
Использование AVX-версии векторной инструкции 128b обнуляет и верхнюю часть reg, поэтому vpxor xmm, xmm, xmm
это хороший выбор для обнуления YMM (AVX1 / AVX2) или ZMM (AVX512) или любого будущего векторного расширения. vpxor ymm, ymm, ymm
при этом не требует дополнительных байтов для кодирования и работает так же на Intel, но медленнее на AMD до Zen2 (2 мупа). Обнуление AVX512 ZMM потребует дополнительных байтов (для префикса EVEX), поэтому предпочтительнее обнуление XMM или YMM.
Примеры XMM / YMM / ZMM
# Good:
xorps xmm0, xmm0 ; smallest code size (for non-AVX)
pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem.
xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# Good with AVX:
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
#sub-optimal AVX
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg
vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2
# Good with AVX512
vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions.
# Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
# sub-optimal with AVX512 (even without AVX512VL)
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
См. Является ли vxorps-zeroing на AMD Jaguar / Bulldozer / Zen быстрее с регистрами xmm, чем ymm? и
Каков наиболее эффективный способ очистить один или несколько регистров ZMM в Knights Landing?
Наполовину связанный: самый быстрый способ установить значение __m256 для всех битов ONE и эффективно
установить все биты в регистре процессора на 1, также охватывает регистры k0..7
маски AVX512 . SSE / AVX не vpcmpeqd
работает на многих (хотя по-прежнему нужен uop для записи единиц), но AVX512 vpternlogd
для регистров ZMM даже не ломается. Внутри цикла подумайте о копировании из другого регистра вместо повторного создания с помощью мупа ALU, особенно с AVX512.
Но обнуление обходится дешево: xor-обнуление регистра xmm внутри цикла обычно так же хорошо, как и копирование, за исключением некоторых процессоров AMD (Bulldozer и Zen), у которых есть удаление mov для векторных регистров, но все же требуется муп ALU для записи нулей для xor -zeroing.
Некоторые процессоры распознают sub same,same
идиомы обнуления xor
, но все процессоры, которые распознают любые идиомы обнуления, распознаютxor
. Просто используйте, xor
чтобы вам не приходилось беспокоиться о том, какой процессор распознает идиому обнуления.
xor
(в отличие от общепризнанной идиомы обнуления mov reg, 0
) имеет некоторые очевидные и некоторые тонкие преимущества (краткий список, затем я расширю их):
mov reg,0
. (Все процессоры)Меньший размер машинного кода (2 байта вместо 5) всегда является преимуществом: более высокая плотность кода приводит к меньшему количеству промахов в кэше инструкций и лучшей выборке инструкций и, возможно, пропускной способности декодирования.
Преимущество отказа от использования исполнительного модуля для xor в микроархитектурах семейства Intel SnB незначительно, но экономит электроэнергию. Более вероятно, что это будет иметь значение для SnB или IvB, у которых есть только 3 порта выполнения ALU. Haswell и более поздние версии имеют 4 порта выполнения, которые могут обрабатывать целочисленные инструкции ALU, в том числе mov r32, imm32
, поэтому при идеальном принятии решений планировщиком (что не всегда происходит на практике) HSW все еще может поддерживать 4 мупа за такт, даже когда всем им нужен ALU порты исполнения.
См. Мой ответ на другой вопрос об обнулении регистров для получения более подробной информации.
Сообщение в блоге Брюса Доусона, на которое Майкл Петч указал (в комментарии к вопросу), указывает, что xor
это обрабатывается на этапе переименования регистра без необходимости в исполнительной единице (ноль мопов в неиспользуемом домене), но упускает тот факт, что это все еще один муп в слитном домене. Современные процессоры Intel могут выдавать и выводить из эксплуатации 4 микропроцессора с объединенными доменами за такт. Отсюда и ограничение в 4 нуля на такт. Повышенная сложность оборудования для переименования регистров - это только одна из причин ограничения ширины дизайна до 4 (Брюс написал несколько очень отличных сообщений в блоге, таких как его серия по математике FP и проблемам x87 / SSE / округления , что я и делаю. настоятельно рекомендую).
На AMD Bulldozer семейство процессоров , mov immediate
работает на один и те же EX0 / EX1 порты выполнения целого , как xor
. mov reg,reg
также может работать на AGU0 / 1, но только для копирования регистров, а не для немедленной настройки. Так AFAIK, на AMD единственное преимущество xor
над mov
является короче кодирования. Это также может сэкономить ресурсы физических регистров, но я не видел никаких тестов.
Признанные идиомы обнуления позволяют избежать штрафов за частичные регистры на процессорах Intel, которые переименовывают частичные регистры отдельно от полных регистров (семейства P6 и SnB).
xor
будет помечать регистр как имеющие верхние части обнуляются , поэтому xor eax, eax
/ inc al
/ inc eax
избегает обычного штрафа частичного регистра , который предварительно IVB процессоров имеют. Даже без xor
этого IvB нужен только uop слияния, когда изменяются старшие 8bits ( AH
), а затем читается весь регистр, и Haswell даже удаляет это.
Из руководства Agner Fog по микроархитектуре, стр. 98 (раздел Pentium M, на который ссылаются более поздние разделы, включая SnB):
Процессор распознает XOR регистра с самим собой как обнуление. Специальный тег в регистре запоминает, что старшая часть регистра равна нулю, так что EAX = AL. Этот тег запоминается даже в цикле:
; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL
(из стр. 82): процессор запоминает, что верхние 24 бита EAX равны нулю, пока вы не получите прерывание, неверное предсказание или другое событие сериализации.
pg82 этого руководства также подтверждает, что mov reg, 0
он не распознается как идиома обнуления, по крайней мере, в ранних проектах P6, таких как PIII или PM. Я был бы очень удивлен, если бы они потратили транзисторы на его обнаружение на более поздних процессорах.
xor
устанавливает флаги , что означает, что вы должны быть осторожны при тестировании условий. Поскольку setcc
, к сожалению, он доступен только с 8-битным адресатом , обычно вам нужно избегать штрафов за частичную регистрацию.
Было бы неплохо, если бы x86-64 перепрофилировал один из удаленных кодов операций (например, AAM) на бит 16/32/64 setcc r/m
, с предикатом, закодированным в 3-битном поле регистра источника поля r / m (способ некоторые другие инструкции с одним операндом используют их как биты кода операции). Но они этого не сделали, да и для x86-32 это не поможет.
В идеале вы должны использовать xor
/ устанавливать флаги / setcc
/ читать полный регистр:
...
call some_func
xor ecx,ecx ; zero *before* the test
test eax,eax
setnz cl ; cl = (some_func() != 0)
add ebx, ecx ; no partial-register penalty here
Это имеет оптимальную производительность на всех ЦП (без остановок, слияния мопов или ложных зависимостей).
Все сложнее, если вы не хотите выполнять xor перед инструкцией по установке флага . например, вы хотите выполнить ответвление по одному условию, а затем установитьcc по другому условию с теми же флагами. например cmp/jle
, sete
и вы либо не имеют запасной регистр, или вы хотите сохранить свою xor
команду из не-принятого пути кода в целом.
Не существует общепризнанных идиом обнуления, не влияющих на флаги, поэтому лучший выбор зависит от целевой микроархитектуры. В Core2 вставка объединяющего мупа может вызвать остановку на 2 или 3 цикла. Вроде бы дешевле на SnB, но я не тратил много времени на измерения. Использование mov reg, 0
/ setcc
привело бы к значительным потерям на старых процессорах Intel и несколько хуже на новых процессорах Intel.
Использование setcc
/ movzx r32, r8
, вероятно, является лучшей альтернативой для семейств Intel P6 и SnB, если вы не можете выполнить xor-zero перед инструкцией по установке флага. Это должно быть лучше, чем повторение теста после обнуления xor. (Даже не считайте sahf
/ lahf
или pushf
/ popf
). IvB может устранить movzx r32, r8
(т.е. обработать это с переименованием регистров без исполнительного блока или задержки, как xor-zeroing). Haswell и более поздние версии удаляют только обычные mov
инструкции, поэтому movzx
берет исполнительный блок и имеет ненулевую задержку, что делает test / setcc
/ movzx
хуже, чем xor
/ test / setcc
, но все же не хуже, чем test / mov r,0
/ setcc
(и намного лучше на старых процессорах).
Использование setcc
/ movzx
без обнуления сначала плохо для AMD / P4 / Silvermont, потому что они не отслеживают зависимости отдельно для подрегистров. Было бы ложное изменение старого значения регистра. Использование mov reg, 0
/ setcc
для обнуления / разрыва зависимостей, вероятно, является лучшей альтернативой, когда xor
/ test / setcc
не является вариантом.
Конечно, если вам не нужно setcc
, чтобы выходной сигнал был шире 8 бит, вам не нужно ничего обнулять. Однако остерегайтесь ложных зависимостей от ЦП, отличных от P6 / SnB, если вы выбираете регистр, который недавно был частью длинной цепочки зависимостей. (И будьте осторожны, чтобы не вызвать частичную остановку регистрации или дополнительную ошибку, если вы вызываете функцию, которая может сохранить / восстановить регистр, часть которого вы используете.)
and
с немедленным нулем не имеет особого значения, поскольку не зависит от старого значения на любых процессорах, о которых я знаю, поэтому он не разрывает цепочки зависимостей. У него нет преимуществ перед xor
и много недостатков.
Это полезно только для написания микробенчмарков, когда вам нужна зависимость как часть теста задержки, но вы хотите создать известное значение путем обнуления и добавления.
См. Http://agner.org/optimize/ для получения подробной информации о микроархитектуре , в том числе о том, какие идиомы обнуления распознаются как нарушение зависимости (например sub same,same
, на некоторых, но не на всех процессорах, в то время xor same,same
как распознается на всех). mov
Действительно разрывает цепочку зависимостей от старого значения регистра (независимо от исходного значения, ноль или нет, потому что так mov
работает). xor
Разрывает цепочки зависимостей только в особом случае, когда src и dest - один и тот же регистр, поэтому он mov
не включен в список специально признанных прерывателей зависимостей. (Кроме того, потому что это не признано идиомой обнуления, с другими преимуществами, которые она несет.)
Интересно, что самая старая конструкция P6 (PPro через Pentium III) не распознавала xor
-zeroing как средство для разрушения зависимостей, а только как идиому обнуления для целей избежания задержек частичного регистра , поэтому в некоторых случаях стоило использовать оба, mov
а затем xor
- обнуление в таком порядке, чтобы разорвать деп, а затем снова обнулить + установить бит внутреннего тега, чтобы старшие биты были равны нулю, поэтому EAX = AX = AL.
См. Пример 6.17 Агнера Фога. в его микроархитектуре pdf. Он говорит, что это также относится к P2, P3 и даже (раннему?) PM. В комментарии к связанному сообщению в блоге говорится, что это был только PPro, но я тестировал Katmai PIII, а @Fanael тестировал на Pentium M, и мы оба обнаружили, что это не нарушает зависимость задержки -связанная imul
цепочка. К сожалению, это подтверждает результаты Агнера Фог.
Если это действительно делает ваш код более приятным или сохраняет инструкции, тогда, конечно, используйте ноль, mov
чтобы не касаться флагов, если вы не создаете проблемы с производительностью, кроме размера кода. Единственная разумная причина отказа от использования флагов - это избегать xor
, но иногда вы можете выполнить xor-zero перед установкой флагов, если у вас есть запасной регистр.
mov
-Нулевое значение впереди setcc
лучше для задержки, чем movzx reg32, reg8
после (кроме Intel, когда вы можете выбирать разные регистры), но хуже для размера кода.
mov reg, src
также разрывает цепочки dep для процессоров OO (независимо от того, является ли src imm32 [mem]
или другим регистром). Это нарушение зависимостей не упоминается в руководствах по оптимизации, потому что это не особый случай, который происходит только тогда, когда src и dest являются одним и тем же регистром. Это всегда происходит с инструкциями, не зависящими от их назначения. (за исключением реализации Intel popcnt/lzcnt/tzcnt
ложного депозита на назначении)
mov
освобождает, только нулевую задержку. Часть «не использовать порт выполнения» обычно не важна. Пропускная способность слитых доменов может легко стать узким местом, особенно. с грузами или магазинами в смеси.
xor r64, r64
что не нужно просто тратить байт. Как вы говорите, xor r32, r32
это лучший выбор, особенно с KNL. См. Раздел 15.7 «Особые случаи независимости» в этом руководстве по микрарху, если вы хотите узнать больше.