Дает ли использование xor reg, reg преимущество перед mov reg, 0?

Есть два хорошо известных способа установить целочисленный регистр в нулевое значение на x86.

Либо

mov reg, 0

or

xor reg, reg

Есть мнение, что второй вариант лучше, так как в коде не сохраняется значение 0, что экономит несколько байт создаваемого машинного кода. Это, безусловно, хорошо — используется меньше кэша инструкций, и иногда это позволяет ускорить выполнение кода. Многие компиляторы создают такой код.

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

add reg, 17
;do something else with reg here
xor reg, reg

Очевидно, что результат xor будет точно таким же, независимо от начального значения регистра. Но способен ли процессор это распознать?

Я попробовал следующий тест в VC++7:

const int Count = 10 * 1000 * 1000 * 1000;
int _tmain(int argc, _TCHAR* argv[])
{
    int i;
    DWORD start = GetTickCount();
    for( i = 0; i < Count ; i++ ) {
        __asm {
            mov eax, 10
            xor eax, eax
        };
    }
    DWORD diff = GetTickCount() - start;
    start = GetTickCount();
    for( i = 0; i < Count ; i++ ) {
        __asm {
            mov eax, 10
            mov eax, 0
        };
    }
    diff = GetTickCount() - start;
    return 0;
}

При выключенной оптимизации оба цикла занимают одинаковое время. Является ли это разумным доказательством того, что процессор распознает отсутствие зависимости инструкции xor reg, reg от более ранней инструкции mov eax, 0? Что может быть лучшим тестом, чтобы проверить это?


person sharptooth    schedule 16.07.2009    source источник
comment
Я думаю, именно поэтому мы используем языки высокого уровня. Если вы действительно хотите знать, просто измените этап кодегена, чтобы сделать то или иное. Ориентир. Выберите лучшее.   -  person jrockway    schedule 16.07.2009
comment
ах, старый xor reg, reg трюк - старые добрые времена :)   -  person Nick Dandoulakis    schedule 16.07.2009
comment
Я думаю, что архитектура x86 явно определяет XOR reg,reg как нарушение зависимости от reg. См. руководство по архитектуре Intel. Я ожидаю, что MOV reg,... сделает то же самое просто потому, что это MOV. Таким образом, ваш реальный выбор состоит в том, какой из них занимает меньше места (я полагаю, что время выполнения одинаково), если вас не волнуют биты состояния (XOR повреждает их все).   -  person Ira Baxter    schedule 21.07.2009
comment
ваша переменная Count переполнена, поэтому циклы будут выполняться гораздо меньше циклов, чем вы ожидали   -  person phuclv    schedule 06.12.2013
comment
В более поздних микроархитектурах xor reg,reg не требует исполнительного блока (обрабатывается при декодировании?). Он ломает зависимости от reg, и частичное обновление флагов останавливается. И имеет меньшую кодировку. Нет веских причин для подхода mov в последних версиях x86-64, если только вам не нужно сохранять флаги [e].   -  person Brett Hale    schedule 10.02.2014
comment
Есть несколько тонких преимуществ, помимо размера кода, в использовании общепризнанной идиомы обнуления, такой как xor, по сравнению с mov. Я написал ответ на более свежий вопрос, прежде чем увидел этот: stackoverflow.com/questions/33666617/ . Я думаю, что это лучший и более полный ответ, чем любой из них. В идеале они должны быть помечены как дубликаты друг друга.   -  person Peter Cordes    schedule 19.01.2016


Ответы (6)


актуальный ответ для вас:

Справочное руководство по оптимизации архитектур Intel 64 и IA-32

Раздел 3.5.1.8 — это то место, где вы хотите искать.

Короче говоря, есть ситуации, когда xor или mov могут быть предпочтительнее. Проблемы сосредоточены вокруг цепочек зависимостей и сохранения кодов условий.

person Mark    schedule 16.07.2009
comment
Не похоже, что цитируемый текст рекомендует использовать MOV в любой ситуации. - person mwfearnley; 07.05.2016
comment
@mwfearnley К сожалению, Аддисон решил отредактировать мой ответ и выбрать подмножество контента, неясно, почему это было сделано. Вы должны прочитать полные документы, которые охватывают ситуации, когда mov предпочтительнее. - person Mark; 09.05.2016
comment
Спасибо за разъяснения. Я предполагаю, что это была попытка избежать проблемы с перемещением/изменением документа, но, к сожалению, цитата не содержала всех необходимых пунктов. Теперь я вижу из этого раздела, там говорится использовать MOV, когда вы хотите избежать установка кодов состояния. - person mwfearnley; 09.05.2016
comment
@mwfearnley: Редко, когда вы не можете просто выполнить xor-zero перед установкой флагов. См. мой ответ на более свежий xor вопрос с некоторыми предложениями о том, как избежать mov reg, 0 при подготовке к setcc. (И подробнее обо всех преимуществах xor-zeroing). mov reg,0 / setcc ужасно работает на старых процессорах Intel, где чтение полного регистра вызывает остановку частичного регистра, которой xor избегает. - person Peter Cordes; 09.05.2016

На современных процессорах предпочтительнее использовать шаблон XOR. Он меньше и быстрее.

На самом деле меньший размер имеет значение, потому что во многих реальных рабочих нагрузках одним из основных факторов, ограничивающих производительность, являются промахи i-cache. Это не будет отражено в микротесте, сравнивающем два варианта, но в реальном мире это заставит код работать немного быстрее.

И, не обращая внимания на сокращение количества промахов i-кэша, XOR на любом процессоре за последние много лет работает с той же скоростью или даже быстрее, чем MOV. Что может быть быстрее, чем выполнение инструкции MOV? Не выполняя никаких инструкций! В последних процессорах Intel логика отправки/переименования распознает шаблон XOR, «понимает», что результат будет равен нулю, и просто указывает регистр на физический нулевой регистр. Затем он отбрасывает инструкцию, потому что нет необходимости ее выполнять.

Конечным результатом является то, что шаблон XOR использует нулевые ресурсы выполнения и может на последних процессорах Intel «выполнять» четыре инструкции за цикл. MOV достигает максимума в три инструкции за цикл.

Подробнее см. в этом сообщении в блоге, которое я написал:

https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/

Большинству программистов не следует беспокоиться об этом, но разработчикам компиляторов стоит беспокоиться, и хорошо понимать генерируемый код, и это чертовски круто!

person Bruce Dawson    schedule 19.03.2015
comment
Отличная запись! Интересно, существует ли такой же шаблон на Thumb. - person Asti; 16.01.2021
comment
Вполне вероятно, что такая же оптимизация есть и на Thumb. Оптимизация применима к любому неисправному процессору и должна экономить энергию, а иногда и повышать производительность. Но я не знаю. - person Bruce Dawson; 17.01.2021

x86 имеет инструкции переменной длины. MOV EAX, 0 требует на один или два байта больше в кодовом пространстве, чем XOR EAX, EAX.

person ajs410    schedule 15.10.2009
comment
mov eax, 0 составляет 5 байтов: один для кода операции mov eax, imm32 и 4 для 4 байт немедленных данных. xor eax, eax составляет 2 байта: один код операции xor r32, r/m32, один для операндов. - person Peter Cordes; 12.12.2015

Я перестал чинить свои машины после того, как продал свой универсал HR 1966 года. У меня похожее исправление с современными процессорами :-)

Это действительно будет зависеть от основного микрокода или схемы. Вполне возможно, что ЦП мог бы распознать "XOR Rn,Rn" и просто обнулить все биты, не беспокоясь о содержимом. Но, конечно, то же самое можно сделать и с "MOV Rn, 0". Хороший компилятор в любом случае выберет лучший вариант для целевой платформы, поэтому обычно это проблема, только если вы кодируете на ассемблере.

Если ЦП достаточно умен, ваша зависимость XOR исчезнет, ​​поскольку он знает, что значение не имеет значения, и все равно установит его равным нулю (опять же, это зависит от фактического используемого ЦП).

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

person paxdiablo    schedule 16.07.2009
comment
Независимо от того, является ли это чрезмерной оптимизацией для практического использования, может быть полезно понять, что не все похожие инструкции созданы одинаковыми. ;) - person jerryjvl; 16.07.2009
comment
@jerryjvl - Также полезно понимать, что современные процессоры x86 для настольных ПК не выполняют машинный код x86 - они декодируют x86 в RISC, например, внутренние инструкции для выполнения. Таким образом, они могут распознавать общие кодовые последовательности (такие как xor eax, eax) и преобразовывать их в более простые инструкции, например, вместо этого в какую-нибудь четкую инструкцию reg. Фактический xor, вероятно, не выполняется в этом случае. - person Michael; 16.07.2009
comment
микро-оптимизация, возможно, должна сойти с ума, когда вы пишете MBR =). - person brianmearns; 25.03.2013
comment
@sh1ftst0rm: в наши дни только неумные люди делают такие вещи. - person Daniel Kamil Kozar; 06.05.2014

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

Также обратите внимание, что по памяти состояние флагов не идентично между этими методами, но я могу ошибаться.

person jerryjvl    schedule 16.07.2009

Вы пишете компилятор?

И, во-вторых, ваш бенчмаркинг, вероятно, не сработает, поскольку у вас там есть ветка, которая, вероятно, все равно занимает все время. (если только ваш компилятор не развернет цикл за вас)

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

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

person Thomas    schedule 16.07.2009
comment
Весь этот веб-сайт имеет качество, которое заботит остальной мир. Не думаю, что это был бы хороший ответ. - person Roman Starkov; 21.01.2011
comment
Кажется, вы и другие сосредотачиваетесь на том, что, как я полагаю, вы считаете оскорбительным. Я удалил эту часть, так как я думаю, что вы и другие никогда не читали дальше этого и просто проголосовали против. - person Thomas; 09.07.2019
comment
Для Sandybridge/Ivybridge вы можете довольно легко построить цикл, который выполняется с частотой 1 итерация за такт с nop или xor same,same, но узким местом является пропускная способность исполнительного блока ALU с mov reg,0. Более поздние процессоры Intel имеют 4 исполнительных блока ALU, поэтому конкретный пример исключения xor-zero, создающего измеримую разницу, отличную от размера кода, построить намного труднее. (xorps обнуление регистров xmm/ymm по-прежнему легко, потому что портов векторного ALU меньше, чем ширина фронтенда). И процессоры AMD не устраняют внутреннюю операцию, поэтому преимущество на самом деле заключается только в размере кода. - person Peter Cordes; 10.07.2019
comment
Большая часть кода в большинстве случаев попадает в кэш L1i. Промахи кэша L1i случаются, но большинство инструкций, выполняемых в ходе программы, поступают из кэша L1i или даже из меньшего/быстрого кэша uop. Большинство программ проводят много времени в циклах малого и среднего размера. Кэши работают. - person Peter Cordes; 10.07.2019
comment
Однако вы правы в том, что попытка ОП провести бенчмаркинг вряд ли сработает. Но это может быть на Sandybridge, если накладные расходы на цикл составляют 2 дополнительных uop ALU, что составляет 4 общих uop переднего плана. Если одним из них является xor-zeroing, который можно устранить, бэкэнд справится с этим. - person Peter Cordes; 10.07.2019
comment
Я согласен со всем, что ты сказал. Я не в курсе существования остановок AGI или их эквивалентов в современных процессорах Intel в дополнение к узким местам ALU, но суть остается прежней: вы не можете протестировать инструкцию так, как это делает op. Это зависит от всего окружающего его кода, и ветвь является лишь его частью. Моя точка зрения, что методология и почему? остается прежним, а только усиливается но то, что вы добавили. - person Thomas; 11.07.2019