Как лучше всего установить регистр в ноль в сборке x86: xor, mov или and?

120

Все следующие инструкции делают то же самое: установить %eaxна ноль. Какой способ оптимален (требует наименьшего числа машинных циклов)?

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax
balajimc55
источник
6
Возможно, вы захотите прочитать эту статью
Майкл Петч
1
xor vs mov: stackoverflow.com/questions/1135679/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ответы:

222

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.


Что особенного в обнулении идиом вроде 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 микропроцессора с объединенными доменами за такт. Отсюда и ограничение в 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цепочка. К сожалению, это подтверждает результаты Агнера Фог.


TL: DR:

Если это действительно делает ваш код более приятным или сохраняет инструкции, тогда, конечно, используйте ноль, movчтобы не касаться флагов, если вы не создаете проблемы с производительностью, кроме размера кода. Единственная разумная причина отказа от использования флагов - это избегать xor, но иногда вы можете выполнить xor-zero перед установкой флагов, если у вас есть запасной регистр.

mov-Нулевое значение впереди setccлучше для задержки, чем movzx reg32, reg8после (кроме Intel, когда вы можете выбирать разные регистры), но хуже для размера кода.

Питер Кордес
источник
7
Большинство арифметических инструкций OP R, S вынуждаются вышедшим из строя CPU ожидать, пока содержимое регистра R не будет заполнено предыдущими инструкциями с регистром R в качестве цели; это зависимость данных. Ключевым моментом является то, что у чипов Intel / AMD есть специальное оборудование для разрыва зависимостей «должен ждать данных» в регистре R при обнаружении XOR R, R, и это не обязательно для других инструкций обнуления регистров. Это означает, что инструкцию XOR можно запланировать для немедленного выполнения, и именно поэтому Intel / AMD рекомендуют ее использовать.
Ira Baxter
3
@IraBaxter: Ага, и просто чтобы избежать путаницы (потому что я видел это заблуждение по SO), mov reg, srcтакже разрывает цепочки dep для процессоров OO (независимо от того, является ли src imm32 [mem]или другим регистром). Это нарушение зависимостей не упоминается в руководствах по оптимизации, потому что это не особый случай, который происходит только тогда, когда src и dest являются одним и тем же регистром. Это всегда происходит с инструкциями, не зависящими от их назначения. (за исключением реализации Intel popcnt/lzcnt/tzcntложного депозита на назначении)
Питер Кордес,
2
@Zboson: «Задержка» инструкции без зависимостей имеет значение только в том случае, если в конвейере был пузырь. Это хорошо для исключения mov, но для обнуления инструкций преимущество нулевой задержки вступает в игру только после чего-то вроде неверного предсказания ветвления или пропуска I $, когда выполнение ожидает декодированных инструкций, а не готовности данных. Но да, mov-elimination не movосвобождает, только нулевую задержку. Часть «не использовать порт выполнения» обычно не важна. Пропускная способность слитых доменов может легко стать узким местом, особенно. с грузами или магазинами в смеси.
Питер Кордес,
2
По словам Агнера, KNL не распознает независимость 64-битных регистров. Так xor r64, r64что не нужно просто тратить байт. Как вы говорите, xor r32, r32это лучший выбор, особенно с KNL. См. Раздел 15.7 «Особые случаи независимости» в этом руководстве по микрарху, если вы хотите узнать больше.
Z-бозон
3
а, а где старый добрый MIPS с его «нулевым регистром», когда он вам нужен.
hayalci