В каких условиях происходит одно сравнение «если» и «если-иначе» на С, как при сборке?

Для сборки 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*/ } 

Это микрооптимизация, которая на самом деле не нужна. Компилятор сможет выбрать лучшие инструкции и даже сгенерировать одни и те же инструкции для всех ваших случаев.

Some programmer dude 25.08.2024 13:05

Вы тоже можете сделать switch ((x > y) - (x < y)) { case -1: case 0: case 1: }

fuz 25.08.2024 13:09
godbolt.org/z/4zK7Mbdvx
pmg 25.08.2024 13:38

Если у вас есть предварительные знания из проблемной области о том, что один тест разделит входные данные пополам или лучше, а другой тест обрабатывает редкий крайний случай, тогда тест, который выполняет больше всего работы, должен идти первым. Оптимизаторы могут делать некоторые хитрые трюки, если условный код представляет собой простое присваивание, вообще не допускающее ветвей, поэтому микрооптимизация такого рода кода действительно не имеет смысла — оставьте это на усмотрение оптимизирующего компилятора!

Martin Brown 25.08.2024 14:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Обычно в 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

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