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

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

cmp al, 0
je done

и следующее:

or al, al
jz done

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

2 ответа

Решение

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

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

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

Как отметил Шут в комментариях; test al,al избегает обеих проблем - меньше cmp al,0 и не модифицирует EAX,

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

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

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

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


Флаг результатов 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, Любой процессор, который может использовать макрос TEST/JL, также может использовать макрос TEST/JS, даже Core2. Но после CMP byte [mem],0 всегда используйте JL, а не JS для перехода на знаковый бит.

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


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


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

Последние процессоры Intel могут макросить некоторые инструкции (например, and а также add / sub) так же как test а также cmp, но or не один из них. Процессоры AMD могут только объединяться test а также cmp с ОКК. См. X86_64 - Assembly - условия цикла и не в порядке, или просто обратитесь непосредственно к документам по микроарху Agner Fog для получения подробной информации о том, какой процессор может слиться во что-либо. test может макро-предохранитель в некоторых случаях, когда cmp не может, например, с js,

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


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

Однако на процессорах семейства P6 (PPro / PII - Nehalem) написание регистра назначения может быть преимуществом. Для этапа чтения / переименования для чтения из файла постоянного регистра имеется ограниченное количество портов для чтения регистров, но недавно записанные значения доступны непосредственно из ROB. Перезапись регистра без необходимости может снова привести его в действие в сети пересылки, чтобы избежать задержек при чтении из регистра. (См . Микроарху Агнера Фога pdf.

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

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

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

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

ИДК, где 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-относительный + немедленный не будет микроплавким в декодерах, либо индексированные режимы адресации не будут расслоены. В любом случае, ведущий к 3 мопам слитых доменов для 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 достаточно редки, чтобы не стоило тратить код операции. Тем более что это решение было принято для 8086 с 16-битным кодом, где разница была только между imm8 и imm16, а не imm32.)

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

Другие вопросы по тегам