Для сборки x86 инструкция «cmp» устанавливает два флага: «ZF» и «CF», позволяющие определить, равны или больше или меньше два целых числа с помощью одного сравнения. Как написать код на C, чтобы для всех трех случаев выполнялось только одно сравнение? Возможны 6 вариантов:
if (x > y) { /*code1*/ } else if (x < y) { /*code2*/ } else { /*code3*/ }
if (x < y) { /*code2*/ } else if (x > y) { /*code1*/ } else { /*code3*/ }
if (x > y) { /*code1*/ } else if (x == y) { /*code3*/ } else { /*code2*/ }
if (x < y) { /*code2*/ } else if (x == y) { /*code3*/ } else { /*code1*/ }
if (x == y) { /*code3*/ } else if (x < y) { /*code2*/ } else { /*code1*/ }
if (x == y) { /*code3*/ } else if (x > y) { /*code1*/ } else { /*code2*/ }
Вы тоже можете сделать switch ((x > y) - (x < y)) { case -1: case 0: case 1: }
Если у вас есть предварительные знания из проблемной области о том, что один тест разделит входные данные пополам или лучше, а другой тест обрабатывает редкий крайний случай, тогда тест, который выполняет больше всего работы, должен идти первым. Оптимизаторы могут делать некоторые хитрые трюки, если условный код представляет собой простое присваивание, вообще не допускающее ветвей, поэтому микрооптимизация такого рода кода действительно не имеет смысла — оставьте это на усмотрение оптимизирующего компилятора!
Обычно в C мы пишем обычный оператор if/else-if/else и ожидаем, что компилятор его оптимизирует.
Итак, если компилятор знает, что x и y являются простыми значениями, не требующими повторного вычисления, мы можем закодировать на C следующее:
if ( x > y )
/* code1 */
else if ( x < y )
/* code2 */
else // x == y
/* code3 */
и сгенерированная оптимизированная сборка должна выглядеть примерно так:
mov eax, [x]
cmp eax, [y]
jg code1
jl code2
/* code3 */
jmp after
code1:
/* code1 */
jmp after
code2:
/* code2 */
jmp after
after:
Обратите внимание, что в приведенной выше оптимизированной сборке значения x и y загружаются только один раз и сравниваются только один раз.
Вот исходный код и сгенерированная сборка на godbolt:
https://godbolt.org/z/8YxT5Kh7P
Интересуют следующие инструкции:
cmp eax, ebp
jg .L7
jl .L8
(Здесь eax
содержит y, а ebp
содержит x.)
Это пример практики написания кода на языке C, ожидающего, что компилятор оптимизирует его определенным образом, и полагающегося на то, что компилятор его оптимизирует. Однако во многих случаях компилятор решает сделать что-то, чего мы, возможно, не ожидали.
Итак, если у вас появится привычка проверять, действительно ли компилятор сделал именно то, что вы ожидали, будьте готовы иногда удивляться.
Описанный выше метод применим ко всем 6 случаям, перечисленным в вопросе, если x и y являются простыми значениями.
Если x или y требуют переоценки, то этот прием больше не работает, и нужен другой подход, например:
int d = x - y; //where x and y are not simple values
if ( d > 0 )
/* code1 */
else if ( d < 0 )
/* code2 */
else // d == 0, meaning that x == y
/* code3 */
Обратите внимание, что переменная d
, скорее всего, будет полностью оптимизирована компилятором, в результате чего код будет очень похож на тот, что был показан выше.
Я думаю, вам следует позволить компилятору решить, как лучше. Поэтому следует выбирать наиболее читабельный вариант.
Если вы не уверены, вы также можете проверить это с помощью «Compiler Explorer».
Я привел пример здесь: https://godbolt.org/z/v81z3rfMa
Это микрооптимизация, которая на самом деле не нужна. Компилятор сможет выбрать лучшие инструкции и даже сгенерировать одни и те же инструкции для всех ваших случаев.