Я знаю, что инструкции 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 - 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
- это еще один способ записать его.