Ответ 1
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
mov eax, 0 ; does not touch FLAGS, but not faster and takes more bytes
xor al, al ; false dep on some CPUs, not a zeroing idiom.
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). (Задержка задержки обхода Nehalem 2c между целым числом и FP обычно не имеет значения, потому что выполнение вне порядка обычно может скрыть его в начале новой цепочки зависимостей).
На микроархитектурах семейства SnB ни один из вариантов xor -zeroing даже не нуждается в порте исполнения. В AMD и Intel, предшествующих Nehalem P6/Core2, xorps
и pxor
обрабатываются одинаково (как целочисленные инструкции вектора).
При использовании AVX-версии 128-векторной векторной инструкции также обнуляется верхняя часть регистра, поэтому 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 unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# with AVX
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing high 16, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth it to avoid needing vzeroupper.
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
#sub-optimal:
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix for high source reg
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop.
См. . Является ли vxorps -zeroing на AMD Jaguar/Bulldozer/Zen быстрее с регистрами xmm, чем ymm? и
Какой самый эффективный способ очистить один или несколько регистров ZMM в Knights Landing?
Полусвязанный: Самый быстрый способ установить значение __m256 для всех ОДИН битов и
Эффективно установите все биты в регистре ЦПУ на 1 также охватывает регистры маски AVX512 k0..7
.
Что особенного в обнулении идиом, таких как xor, на разных уарчах
Некоторые ЦП распознают sub same,same
как идиому обнуления, как xor
, но все ЦП, которые распознают любые идиомы обнуления, распознают xor
. Просто используйте xor
, чтобы вам не приходилось беспокоиться о том, какой процессор распознает идиому обнуления.
xor
(в отличие от mov reg, 0
- признанная идиома обнуления) имеет некоторые очевидные и некоторые тонкие преимущества (краткий список, затем я остановлюсь на них):
- меньший размер кода, чем
mov reg,0
. (Все процессоры) - избегает частичной регистрации штрафов для последующего кода. (Intel P6-семейство и SnB-семейство).
- не использует исполнительный блок, экономя энергию и освобождая ресурсы выполнения. (Семейство Intel SnB)
- меньший uop (без непосредственных данных) оставляет место в строке кэша uop для соседних инструкций для заимствования при необходимости. (Семейство Intel SnB).
- не использует записи в файле физического регистра. (По крайней мере, семейство Intel SnB (и P4), возможно, и AMD, поскольку они используют аналогичную схему PRF вместо сохранения состояния регистра в ROB, как микроархитектуры семейства Intel P6.)
Меньший размер машинного кода (2 байта вместо 5) всегда является преимуществом: более высокая плотность кода приводит к меньшему количеству пропусков кэша команд, а также к лучшему извлечению команд и, возможно, декодированию полосы пропускания.
Преимущество в том, что не используется исполнительный модуль для xor в микроархитектурах семейства Intel SnB, незначительно, но экономит энергию. Скорее всего, это имеет значение для SnB или IvB, которые имеют только 3 исполнительных порта ALU. У Haswell и более поздних версий есть 4 порта выполнения, которые могут обрабатывать целочисленные инструкции ALU, включая mov r32, imm32
, поэтому при идеальном принятии решений планировщиком (что не всегда происходит на практике) HSW может поддерживать 4 мопа за такт, даже когда они все нужны порты выполнения ALU.
Смотрите мой ответ на другой вопрос об обнулении регистров для более подробной информации.
Сообщение в блоге Брюса Доусона, на которое ссылался Майкл Петч (в комментарии к вопросу), указывает на то, что xor
обрабатывается на этапе переименования регистра без необходимости выполнения модуля (ноль мопов в неиспользованном домене), но пропускает Дело в том, что это еще один моп в слитном домене. Современные процессоры Intel могут выдавать & удалите 4 uops слитых доменов за часы. Вот откуда берутся 4 ноля за такт. Повышенная сложность аппаратного переименования регистров является лишь одной из причин ограничения ширины дизайна до 4. (Брюс написал несколько очень хороших постов в блоге, например, его серии по математике FP и x87/SSE/округлению, что я очень рекомендую).
В процессорах семейства AMD Bulldozermov 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 нужен только объединяющийся моп, когда старшие 8 бит (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
(из pg82): процессор помнит, что старшие 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 вставка объединяющего Uop может вызвать 2 или 3 цикла остановки. Похоже, что на SnB дешевле, но я не тратил много времени, пытаясь измерить. Использование mov reg, 0
/setcc
приведет к значительным потерям на старых процессорах Intel, и все же будет несколько хуже для новых Intel.
Использование setcc
/movzx r32, r8
, вероятно, является лучшей альтернативой для Intel P6 & Семейства SnB, если вы не можете выполнить xor-zero перед инструкцией по установке флага. Это должно быть лучше, чем повторять тест после xor -zeroing. (Даже не рассматривайте 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, потому что они не отслеживают deps отдельно для подрегистров. Там будет ложное депо на старое значение регистра. Использование 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
-zeroing в этом порядке, чтобы разбить dep и затем снова обнулить + установить внутренний бит тега, чтобы старшие биты были равны нулю, поэтому EAX = AX = AL. (См. пример Agner Fog 6.17 в своем микроархиве pdf. Он говорит, что это также относится к P2, P3 и даже (рано?) PM. Комментарий к связанному сообщению в блоге говорит, что только PPro имел это упущение, но я тестировал на Katmai PIII, а @Fanael тестировал на Pentium M, и мы оба обнаружили, что он не нарушает зависимость для цепочки с задержкой imul
. Это, к сожалению, подтверждает результаты Agner Fog.)
Если это действительно делает ваш код более приятным или сохраняет инструкции, то, конечно, обнуляйте с помощью mov
, чтобы не касаться флагов, если вы не представляете проблему с производительностью, отличную от размера кода. Тем не менее, избегание затуманивания флагов - единственная разумная причина не использовать xor
.