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

Существует два известных способа установки целочисленного регистра на нулевое значение на x86.

Либо

mov reg, 0

или

xor reg, reg

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

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

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

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

Я пробовал следующий тест в VС++ 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? Что может быть лучшим испытанием для проверки этого?

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

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

Если процессор достаточно умен, ваша зависимость XOR исчезает, так как она знает, что значение не имеет значения, и все равно будет устанавливать его на ноль (опять же это зависит от используемого процессора).

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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