Проверьте, равен ли регистр нулю с помощью CMP reg, 0 против OR reg, reg?

Есть ли разница в скорости выполнения, используя следующий код:

cmp al, 0
je done

и следующее:

or al, al
jz done

Я знаю, что инструкции JE и JZ одинаковы, а также то, что использование OR дает улучшение размера одного байта. Тем не менее, я также обеспокоен скоростью кода. Похоже, что логические операторы будут быстрее SUB или CMP, но я просто хотел убедиться. Это может быть компромисс между размером и скоростью или беспроигрышный (конечно, код будет более непрозрачным).

Ответ 1

Это зависит от точной кодовой последовательности, конкретного процессора и других факторов.

Основная проблема с or al, al, заключается в том, что она "изменяет" EAX, что означает, что последующая инструкция, которая использует EAX каким-то образом, может остановиться, пока эта инструкция не завершится. Обратите внимание, что условная ветвь (jz) также зависит от инструкции, но производители процессоров выполняют большую работу (предсказание ветвей и спекулятивное выполнение) для смягчения этого. Также обратите внимание, что теоретически возможно, что изготовитель ЦП для проектирования ЦП, который распознает EAX, не изменяется в этом конкретном случае, но есть сотни этих особых случаев, и преимущества распознавания большинства из них слишком малы.

Основная проблема с cmp al,0 заключается в том, что она немного больше, что может означать медленное извлечение команды/избыточное давление в кеше, и (если это цикл) может означать, что код больше не подходит в каком-либо промежуточном буфере процессора,.

Как отметил Шутт в комментариях; test al,al устраняет обе проблемы - она ​​меньше cmp al,0 и не изменяет EAX.

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

Ответ 2

Да, есть разница в производительности.

Лучший выбор для сравнения регистра с нулем на современном x86 test reg, reg (если ZF не устанавливается надлежащим образом инструкцией, устанавливающей reg). Это похоже на AND reg,reg, но без записи адресата.

or reg,reg не может использовать макро-предохранитель, добавляет латентность для всего, что читает его позже, и для получения результата нужен новый физический регистр. (Таким образом, он использует ресурсы переименования регистров, где test не будет, ограничивает окно инструкции не по заказу процессора). (Переписывание dst может быть победой в семействе Intel P6, хотя, см. Ниже.)


flag результаты test reg,reg/AND reg,reg/or reg,reg идентичны cmp reg, 0 во всех случаях (кроме AF):

  • CF = OF = 0, потому что test/and всегда это делает, а для cmp, потому что вычитание нуля не может переполняться или переноситься.
  • ZF, SF, PF задано в соответствии с результатом (т.е. reg): reg&reg для теста или reg - 0 для cmp. Таким образом, вы можете протестировать отрицательные целые числа или без знака с высоким битом, установленным при просмотре SF.

    Или с jl, потому что OF = 0, поэтому условие l (SF!=OF) эквивалентно SF. Каждый CPU, который может макро-предохранитель, TEST/JL также может использовать MEST TEST/JS, даже Core2. Но после CMP byte [mem],0 всегда используйте JL, а не JS, чтобы разветкить бит знака.

(AF после test undefined, но задается в соответствии с результатом для cmp. Я игнорирую это, потому что это действительно неясно: единственными потребителями для AF являются ASCII-настройка упакованного BCD такие как AAS и lahf/pushf.)


test короче для кодирования, чем cmp с немедленным 0, во всех случаях, кроме специального случая cmp al, imm8, который по-прежнему равен двум байтам. Даже тогда test предпочтительнее по причинам макро-слияния (с jle и аналогичным по Core2), и потому что отсутствие немедленного вообще может помочь уменьшить плотность кеш-памяти, оставив слот, который другая команда может занять, если это необходимо больше пространства (SnB-family).


Декодеры процессоров Intel и AMD могут использовать макро-предохранитель test и cmp с некоторыми условными инструкциями ветвления в одну операцию сравнения и ветвления. Это дает максимальную пропускную способность 5 инструкций за цикл, когда происходит макро-слияние, против 4 без макро-слияния. (Для процессоров Intel с Core2.)

Недавние процессоры Intel могут с макросплавкой выполнять некоторые команды (например, and и add/sub), а также test и cmp, но or не является одним из них. Процессоры AMD могут объединять только test и cmp с JCC. См. x86_64 - Условия сборки и выход из строя или просто обратитесь непосредственно к Agarch Fog microarch docs, для получения подробной информации о том, какой CPU может скомпенсировать. test может содержать макро-предохранитель в некоторых случаях, когда cmp не может, например. с js.

Почти все простые операторы ALU (побитовые логические, add/sub и т.д.) запускаются за один цикл. Все они имеют одинаковую "стоимость" при отслеживании их по конвейеру исполнения вне очереди. Intel и AMD тратят транзисторы на то, чтобы сделать быстрые исполнительные блоки для добавления/суб/всего за один цикл. Да, побитовое or или and проще и, вероятно, потребляет меньше энергии, но все равно не может работать быстрее, чем один такт.


Кроме того, как указывает Брендан, or reg, reg добавляет еще один цикл латентности в цепочку зависимостей для следующих инструкций, которые должны считывать регистр.

Тем не менее, на процессорах семейства P6 (PPro/PII на Nehalem), запись целевого регистра может фактически быть преимуществом. Существует ограниченное количество портов чтения регистра для этапа выпуска/переименования для чтения из файла постоянного регистра, но недавно написанные значения доступны непосредственно из ROB. Неправильное переписывание регистра может привести к тому, что он снова будет работать в сети переадресации, чтобы избежать сбоев при чтении регистра. (См. Agar Fog microarch pdf.

Сообщается, что компилятор Delphi использует or eax,eax, что было разумным выбором в то время, предполагая, что стойки с регистрационным чтением более важны, чем удлинение цепочки отрезков для любых читает его далее.

К сожалению, разработчики компилятора в то время не знали будущего, потому что and eax,eax выполняет точно эквивалентно or eax,eax в семействе Intel P6, но хуже на других ургах, потому что and может использовать макро-предохранитель на семействе Сэндибридж.

Для Core2/Nehalem (последние 2 семейства P6-семейства), test может использовать макро-предохранитель, но and не может, поэтому (в отличие от Pentium II/III/M) это компромисс между макросом -fusion и, возможно, уменьшающие записи в стойках с регистрацией. Уклонение от записи в режиме чтения-записи все еще происходит за счет дополнительной задержки, если значение считывается после тестирования, поэтому test может быть лучшим выбором, чем and в некоторых случаях даже до cmov или setcc, а не jcc, или на процессорах без макро-слияния.

Если вы настроите что-то быстро на несколько uarches, используйте test, если профилирование не показывает, что стойки с чтением регистра являются большой проблемой в конкретном случае на Core2/Nehalem, и использование and действительно исправляет его.

IDK, из которого исходила идиома or reg,reg, кроме, может быть, ее короче вводить. Или, возможно, это было специально предназначено для процессоров P6, чтобы переписать регистр преднамеренно, прежде чем использовать его еще немного. Кодеры в то время не могли предсказать, что для этой цели она окажется менее эффективной, чем and. Но, очевидно, мы никогда не должны использовать его в test или and в новом коде. (Там только разница, когда она была непосредственно перед jcc в семействе Sandybridge, но проще забыть о or reg,reg.)


Чтобы проверить значение в памяти, оно отлично подходит к cmp dword [mem], 0, но процессоры Intel не могут устанавливать команды установки флага вручную с макросов, которые имеют как непосредственный, так и операнд памяти. Если вы собираетесь использовать значение после сравнения на одной стороне ветки, вы должны, вероятно, mov eax, [mem]/test eax,eax или что-то в этом роде. Если нет (например, тестирование логического), cmp с операндом памяти в порядке.

Хотя обратите внимание, что некоторые режимы адресации не будут микшироваться либо в семействе SnB: RIP-relative + direct не будет микро-предохранителем в декодерах, или режимы индексированной адресации будут не ламинироваться. В любом случае, это приведет к 3-мя ошибкам в режиме fused-domain для cmp dword [rsi + rcx*4], 0/jne или [rel some_static_location].

Вы также можете проверить значение в памяти с помощью test dword [mem], -1, но не делать этого. Поскольку test r/m16/32/64, sign-extended-imm8 недоступен, это хуже кода, чем cmp для чего-либо большего, чем байты. (Я думаю, что идея дизайна заключалась в том, что если вы хотите протестировать только небольшой бит регистра, просто test cl, 1 вместо test ecx, 1, а примеры использования, такие как test ecx, 0xfffffff0, достаточно редки, чтобы не стоило тратить opcode. Тем более, что это решение было принято для 8086 с 16-битным кодом, где это была только разница между imm8 и imm16, а не imm32.)

Я написал -1, а не 0xFFFFFFFF, поэтому он будет таким же с byte или qword. ~0 - это еще один способ записать его.