Проверьте, равен ли регистр нулю с 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 - 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
был бы другой способ написать это.