Перейти к основному содержимому

SETcc, CMOV и ветвления без прыжков

Разработчику

Контекст: x86/x86-64, синтаксис Intel (NASM). См. управляющие конструкции про флаги и Jcc.

Два способа реагировать на флаги

После CMP или арифметики процессор выставляет ZF, CF, SF, OF и др. Классический путь — условный переход (JE, JG, JB …): меняется RIP, конвейер может «промахнуться» по предсказанию ветвления.

Альтернативы на x86:

  • SETcc — записать в 8-битный регистр или байт памяти 0 или 1 по условию.
  • CMOVccусловно скопировать значение между регистрами, не меняя поток команд.

Обе группы читают те же условия, что и Jcc (E = equal/ZF, NE, L/G знаковые, B/A беззнаковые).


SETcc — флаг в байт

cmp rax, rbx
sete al ; al = 1, если rax == rbx, иначе 0
movzx rax, al ; расширить до 64 бит, если нужно целое 0/1

Частые мнемоники:

МнемоникаУсловие (после cmp A,B)
SETE / SETZравны
SETNE / SETNZне равны
SETL / SETNGEA < B (знаковое)
SETG / SETNLEA > B (знаковое)
SETB / SETNAEA < B (беззнаковое)
SETA / SETNBEA > B (беззнаковое)

Ограничение: операнд назначения — только 8 бит (AL, BL, байт в памяти). Для 32/64-битного 0/1 делают SETcc + MOVZX/MOVSX.

Применение: упаковка логического результата в структуру, подготовка маски, минимизация ветвлений при серии мелких сравнений.


CMOVcc — условный MOV между регистрами

cmp rdi, rsi
mov rax, rdi
cmovl rax, rsi ; если rdi < rsi (знаковое), rax := rsi

CMOV не допускает память в обоих операндах (только регистр ↔ регистр в типичных формах). Условие ложно — значение в приёмнике не меняется, поэтому перед CMOV часто копируют «значение по умолчанию»:

mov rax, rdi ; предположим «максимум = rdi»
cmp rdi, rsi
cmovl rax, rsi ; если rsi больше — взять rsi

Так реализуют max(a,b) и min(a,b) без двух меток и прыжков.


Когда что выбирать

СитуацияПодход
Большие разные ветки кодаJcc — меньше инструкций
Короткая выборка между двумя значениямиCMOV
Нужен 0/1 в памяти или массив флаговSETcc
Глубокий конвейер, непредсказуемые ветвииногда выгоднее CMOV (зависит от CPU и профиля)
Код, критичный к побочным эффектам чтенияосторожно: CMOV всё равно выполняет оба операнда с точки зрения микроархитектуры в старых моделях; для памяти с побочными эффектами используйте ветвление

Современные компиляторы генерируют CMOV для тернарного оператора a < b ? b : a на простых типах.


Связь с длинной арифметикой

В длинном сложении финальный перенос за старший разряд удобно сохранить так:

setc al
movzx rax, al ; 1 = было переполнение беззнакового сложения

Вместо перехода на метку «overflow».


Чего избегать

  • SETcc в 32-битный регистр напрямую — такой формы нет; только 8 бит.
  • Путаница знакового и беззнакового условия — те же правила, что для JL и JB в типах данных.
  • CMOV вместо Jcc при вызове функций — если ветка вызывает разный код, нужен переход, а не условное копирование.

См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).