Операторы сравнения с логическими значениями трюк

#c #boolean #comparison-operators #boolean-operations

#c #логическое #операторы сравнения #логические операции

Вопрос:

В C существуют логические операторы amp;amp; , || , ! , которые соответствуют соединению, дизъюнкции, отрицанию соответственно.

Но я заметил, что операторы сравнения == , != , < , > , <= , >= могут использоваться и для логических значений! Учитывая, что P и Q являются логическими значениями:

P == Q является двузначным,

P != Q является исключающей дизъюнкцией,

P < Q является ли обратное неимпликацией,

P > Q является неимпликацией,

P <= Q является следствием,

И P >= Q является обратным подтекстом .

Мои вопросы:

  1. Будет ли программа работать лучше с помощью этого трюка?
  2. Есть ли какой-нибудь пример кода, использующего этот трюк (на любом языке)?

Комментарии:

1. Что с приведениями? Почему бы и нет P < Q , P > Q , P <= Q , P >= Q ?

2. О, я не знал, что <, >, <=, >= можно использовать и для логических значений. Спасибо.

3. «Увеличится ли производительность благодаря этому трюку?» Увеличение по сравнению с чем?

4. Скорость. Разве это не было очевидно?

5. Я имею в виду по сравнению с использованием только кода amp;amp; , || и ! ?

Ответ №1:

Увеличит ли производительность этот трюк?

На любом процессоре, где это имело бы какую-либо выгоду, когда P и Q являются простыми переменными, это было бы достаточно просто, чтобы вы могли ожидать, что компиляторы будут использовать его уже без необходимости переписывания исходного кода.

Но имейте в виду, что P < Q в целом это имеет явный недостаток !P amp;amp; Q : это требует оценки Q , когда результат уже известен, если P вычисляется true . То же самое относится ко всем другим операторам отношения.

Есть ли какой-нибудь пример кода, использующего этот трюк (на любом языке)?

Не как трюк, а потому, что это, возможно, приводит к более понятному коду (а не к какому-либо конкретному языку):

 if ((a == null) != (b == null))
  throw "a and b must either both be null, or both be non-null";
 

Это может быть написано с ^ помощью . Что легче читать, зависит от мнения.

Комментарии:

1. Из-за недостатка операторов сравнения по сравнению с логическими операторами я опубликовал предложение по оптимизации в предложении ISO C .

2. Удачи, но, честно говоря, я не ожидаю, что от этого что-то произойдет: вы предлагаете добавить специальное исключение к правилам языка, которое потенциально изменяет поведение существующего кода, и не даете никаких убедительных вариантов его использования. Какую выгоду вы видите в P < Q over !P amp;amp; Q ?

Ответ №2:

На самом деле я думаю, что это может ускорить код. Вот код для первой функции:

 bool biconditional(bool a, bool b)
{
    return (a amp;amp; b) || (!a amp;amp; !b);
}

bool biconditional_trick(bool a, bool b)
{
    return a == b;
}
 

И сгенерированная сборка:

 biconditional(bool, bool):
        mov     eax, esi
        xor     eax, 1
        xor     eax, edi
        ret
biconditional_trick(bool, bool):
        cmp     dil, sil
        sete    al
        ret
 

Я использовал gcc 5.3 из Compiler Explorer с флагами -O3 -Wall -fno-verbose-asm -march=haswell .

Очевидно, что вы можете срезать 1 инструкцию. Интересно, что gcc не выполняет эту оптимизацию. Возможно, вы захотите отправить им электронное письмо и спросить, почему: https://gcc.gnu.org/lists.html

Редактировать: другой ответ имеет смысл: логические выражения можно вычислять быстрее, обрезая ненужные части. Чтобы продемонстрировать, я переписал код, чтобы использовать вызовы функций, которые возвращают bool вместо bool аргументов:

 bool fa();
bool fb();

bool biconditional_with_function()
{
    return (fa() amp;amp; fb()) || (!fa() amp;amp; !fb());
}

bool biconditional_with_function_trick()
{
    return fa() == fb();
}
 

Вот сборка:

 biconditional_with_function():
        sub     rsp, 8
        call    fa()
        test    al, al
        je      .L7
        call    fb()
        test    al, al
        jne     .L10
.L7:
        call    fa()
        mov     edx, eax
        xor     eax, eax
        test    dl, dl
        je      .L14
.L10:
        add     rsp, 8
        ret
.L14:
        call    fb()
        add     rsp, 8
        xor     eax, 1
        ret
biconditional_with_function_trick():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        cmp     bl, al
        pop     rbx
        sete    al
        ret
 

Вы можете видеть, что код, сгенерированный для biconditional_with_function uses, переходит, чтобы пропустить вторую половину выражения, если первая половина истинна. Интересно, что когда вторая половина вычисляется fa() и fb() вызывается в целом дважды, поскольку компилятор не знает, всегда ли они возвращают один и тот же результат. Если это так, код следует переписать, сохранив оцененные результаты в их собственных переменных:

 bool biconditional_with_function_rewritten()
{
    bool a = fa();
    bool b = fb();
    return (a amp;amp; b) || (!a amp;amp; !b);
}
 

И сборка:

 biconditional_with_function_rewritten():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        xor     eax, 1
        xor     eax, ebx
        pop     rbx
        ret
 

Мы видим, что они почти идентичны, остается только разница в 1 инструкции, что дает методу «трюк» небольшое преимущество.

Для обратного неимпликации мы можем видеть, что действительно GCC избегает вычисления второго оператора при использовании логических операторов, но не при использовании < оператора:

 bool fa();
bool fb();

bool converse_nonimplication()
{
    return !fa() amp;amp; fb();
}

bool converse_nonimplication_trick()
{
    return fa() < fb();
}
 

Сборка:

 converse_nonimplication():
        sub     rsp, 8
        call    fa()
        test    al, al
        je      .L5
        xor     eax, eax
        add     rsp, 8
        ret
.L5:
        add     rsp, 8
        jmp     fb()
converse_nonimplication_trick():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        cmp     al, bl
        pop     rbx
        seta    al
        ret
 

Комментарии:

1. Хм, действительно. В первом примере есть старый отчет об ошибке с пропущенной оптимизацией для GCC, хотя код, который генерируется сейчас, уже намного лучше, чем тогда. Это не так просто, как «чем меньше инструкций, тем быстрее», поэтому, возможно, стоит проверить фактические затраты на инструкции для обеих версий, но если они не работают одинаково быстро, в одной или другой есть пропущенная оптимизация.