2015-11-12 4 views
74

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

xorl %eax, %eax 
mov $0, %eax 
andl $0, %eax 
+3

Возможно, вы захотите прочитать эту статью [https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/) –

+1

xor vs mov: http : //stackoverflow.com/questions/1135679/does-using-xor-reg-reg-give-advantage-over-mov-reg-0 –

ответ

148

TL; DR резюме: xor same, same является лучшим выбором для всех процессоров. Никакой другой метод не имеет никакого преимущества перед ним, и он имеет по крайней мере некоторое преимущество перед любым другим способом. Это официально рекомендовали Intel и AMD. В 64-битном режиме все еще используйте xor r32, r32, потому что writing a 32-bit reg zeros the upper 32. xor r64, r64 - это пустой байт, потому что ему нужен префикс REX.

Поиск нужного векторного регистра обычно лучше всего сделать с помощью pxor xmm, xmm. Обычно это то, что делает gcc (даже перед использованием с инструкциями FP).

xorps xmm, xmm может иметь смысл. Это один байт короче, чем pxor, но xorps нужен порт выполнения 5 на Intel Nehalem, а pxor может работать на любом порту (0/1/5). (Задержка задержки байпаса Nehalem между целыми и FP обычно не актуальна, поскольку выполнение вне порядка обычно может скрыть ее в начале новой цепочки зависимостей).

В микроархитектурах семейства SnB ни один из вариантов обхода нуля не требует исполнения порта. В AMD и pre-Nehalem P6/Core2 Intel, xorps и pxor обрабатываются одинаково (как векторные целые инструкции).

Использование версии AVX вектора векторной команды 128b также является верхней частью регистра, поэтому vpxor xmm, xmm, xmm является хорошим выбором для обнуления YMM (AVX1/AVX2) или ZMM (AVX512) или любого будущего векторного расширения. vpxor ymm, ymm, ymm не требует лишних байтов для кодирования и работает одинаково. Для обнуления ZMM AVX512 потребуются дополнительные байты (для префикса EVEX), поэтому предпочтение следует отдавать XMM или YMM.


Некоторые процессоры признают sub same,same как обнуление идиомы, как xor, но все процессоры, которые распознают любые обнуление идиомы признают xor. Просто используйте xor, поэтому вам не нужно беспокоиться о том, какой процессор распознает, какая идиома обнуляется.

xor (будучи признанным обнулением идиома, в отличии от mov reg, 0) имеют некоторые очевидные и некоторые тонкие преимущества (сводный список, то я буду расширять на них):

  • меньшего кодового размер, чем mov reg,0. (Все процессоры)
  • позволяет избежать штрафов с частичным регистратором для последующего кода. (Семейство Intel P6 и семейство SnB).
  • не использует исполнительный блок, экономя энергию и освобождая ресурсы выполнения. (Intel SnB-family)
  • меньший uop (без непосредственных данных) оставляет место в кэше кэш-линии для ближайших инструкций по заимствовать при необходимости. (Семейство Intel SnB).
  • doesn't use up entries in the physical register file. (Intel SnB-family (и P4), по крайней мере, возможно, AMD, так как они используют аналогичный проект PRF вместо сохранения состояния регистрации в ROB, таком как микроархитектура семейства Intel P6.)

Меньший размер машины код (2 байта вместо 5) всегда является преимуществом: Более высокая плотность кода приводит к меньшему количеству промахов кэша инструкций, и принеси лучше инструкции и потенциально декодировать пропускную способность.


Выгода не используется блоком выполнения для исключающего на Intel SnB семейство микроархитектура незначительна, но позволяет экономить энергию. Скорее всего, это имеет значение на SnB или IvB, у которых есть только три порта выполнения ALU. Haswell и позже имеют 4 порта выполнения, которые могут обрабатывать целые инструкции ALU, в том числе mov r32, imm32, поэтому при идеальном принятии решений планировщиком (что не происходит на практике), HSW все равно может поддерживать 4 часа в час, даже когда все они нуждаются в выполнении порты.

Для получения более подробной информации см. my answer on another question about zeroing registers.

Bruce Dawson's blog post, что Майкл Petch связаны (в комментарии по этому вопросу) указывает на то, что xor обрабатывается на этапе регистровой переименовывать без необходимости блок исполнения (ноль Uops в слитых области), но пропустил тот факт, что он по-прежнему один uop в объединенном домене. Современные процессоры Intel могут выпускать & уйти в отставку с 4-мя фьюзическими доменами за часы. Вот откуда берутся 4 нули за лимит часов. Повышенная сложность аппаратного переименования регистров является лишь одной из причин ограничения ширины дизайна до 4. (Брюс написал несколько очень хороших сообщений в блоге, например, свою серию на FP math and x87/SSE/rounding issues, которую я очень рекомендую).


На 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, когда изменяются высокие 8 бит (AH), а затем считывается весь регистр, а Haswell даже удаляет это.

От microarch руководства Agner тумана, в пг 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 равны нулю, пока вы не получаете прерывания, misprediction или другой событие сериализации.

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 перед инструкцией по установке флага. например вы хотите разветвиться на одном условии, а затем setcc на другое условие из тех же флагов. например cmp/jle, sete, и у вас либо нет запасного регистра, либо вы хотите полностью удалить xor из незанятого кода.

Нет признанных идиом обнуления, которые не влияют на флаги, поэтому лучший выбор зависит от целевой микроархитектуры. На Core2 вставка слияния uop может привести к остановке в 2 или 3 цикла. Это дешевле на SnB, но я не тратил много времени, пытаясь измерить. Использование mov reg, 0/setcc будет иметь существенный штраф для более старых процессоров Intel и все еще будет несколько хуже на более новой Intel.

Использование setcc/movzx r32, r8, вероятно, является лучшей альтернативой для семейств Intel P6 & SnB, если вы не можете xor-zero перед инструкцией по установке флага. Это должно быть лучше, чем повторять тест после обнуления нуля. (Даже не рассматривайте sahf/lahf или pushf/popf). IvB может устранить movzx r32, r8 (т. Е. Обрабатывать его с помощью переименования регистров без блока выполнения или латентности, например, xor-zeroing). Haswell, а затем устранить только регулярные mov инструкции, так movzx принимает исполнительный блок и имеет ненулевую задержку, что делает тест/setcc/movzx хуже xor/тест/setcc, но до сих пор, по крайней мере так же хорошо, как тест/mov r,0/setcc (и намного лучше на старых процессорах).

Использование setcc/movzx без обнуления сначала плохое для AMD/P4/Silvermont, поскольку они не отслеживают оттиски отдельно для субрегистров. Было бы ложное описание старого значения регистра. Использование mov reg, 0/setcc для обнуления/зависимости может быть наилучшей альтернативой, когда xor/test/setcc не является вариантом.

Конечно, если вы не хотите, чтобы выходной сигнал setcc был шире 8 бит, вам не нужно ничего нуля. Однако будьте осторожны с ложными зависимостями от CPU, отличных от P6/SnB, если вы выберете регистр, который недавно был частью длинной цепи зависимостей. (И остерегайтесь вызывает частичную рег стойло или дополнительный моп, если вы вызываете функцию, которая может сохранить/восстановить регистр, который вы используете часть.)


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

См http://agner.org/optimize/ для microarch документации, в том числе, которые обнуление идиомы признаются как нарушение зависимостей (например, sub same,same на некоторых, но не всех процессоров, в то время как xor same,same признается на всех.) mov делает разорвать цепочку зависимостей на старое значение register (независимо от исходного значения, ноль или нет, так как это работает mov). xor только разрывает цепи зависимостей в специальном случае, где src и dest являются одним и тем же регистром, поэтому mov исключен из списка специально признанных зависимых выключателей. (Кроме того, поскольку он не признается в качестве обнуления идиомы, с другими преимуществами, которые несут.)

Интересно, что самый старый дизайн P6 (PPro) не признать xor -zeroing как зависимость выключатель, только идиома с нулевым значением для избежания неполных регистров, поэтому в некоторых случаях стоило использовать как. (См. Пример 6.17 Агнера Фога в своем микроархите pdf. Он утверждает, что это относится также к P2, P3 и даже (раннему?) PM, но я скептически отношусь к этому. A comment on the linked blog post говорит, что это был только PPro, который имел этот надзор. кажется, действительно маловероятно, что несколько поколений семьи P6 существовали без признания исключающего обнуления как DEP выключатель.)


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

+0

Интересно. Так что это не на 100% бесплатно. Я имею в виду, что даже если он не использует порт, он по-прежнему стоит микро-опера. Это тонкость, которую я пропустил в руководстве Агнера. Благодаря! Таким образом, он имеет нулевую задержку, но пропускная способность равна 4 (или 0,25 обратная пропускная способность). –

+5

Большинство арифметических команд OP R, S принудительно выходят из строя CPU, чтобы дождаться, когда содержимое регистра R будет заполнено предыдущими инструкциями с регистром R в качестве цели; это зависимость данных.Ключевым моментом является то, что чипы Intel/AMD имеют специальное оборудование для * break * must-wait-for-data-dependencies в регистре R при обнаружении XOR R, R и не обязательно это делают для других инструкций об установлении нуля. Это означает, что инструкция XOR может быть запланирована для немедленного выполнения, и именно поэтому Intel/AMD * рекомендуют * использовать ее. –

+1

@ IraBaxter: Yup, и просто чтобы избежать путаницы (потому что я видел это заблуждение на SO), 'mov reg, src' также разрывает цепочки отрезков для OO-процессоров (независимо от src, являющегося imm32,' [mem] 'или другой регистр). Это нарушение зависимостей не упоминается в руководствах по оптимизации, поскольку это не особый случай, когда это происходит только тогда, когда src и dest являются одним и тем же регистром. Это всегда * происходит для инструкций, которые не зависят от их dest. (за исключением реализации Intel 'popcnt/lzcnt/tzcnt' с ложным оттиском в dest.) –