Операторы сравнения на булевом трюке

В C ++ логические операторы &&, ||, ! есть, что соответствует соединению , дизъюнкция , отрицание , соответственно.

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

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

P != Q это эксклюзивный дизъюнкция ,

P < Q обратное неявное ,

P > Q это не подтекст ,

P <= Q это подразумевается ,

А также P >= Q обратное значение .

Мои вопросы:

  1. Будет ли программа работать лучше с помощью этого трюка?

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

4

Решение

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

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

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

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

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

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

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

4

Другие решения

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

bool biconditional(bool a, bool b)
{
return (a && b) || (!a && !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 из Проводник компилятора с флагами -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() && fb()) || (!fa() && !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 использует прыжки, чтобы пропустить вторую половину выражения, если первая половина истинна. Интересно, когда вторая половина оценивается, fa() а также fb() Вызываются в целом дважды, так как компилятор не знает, всегда ли они возвращают один и тот же результат. Если это так, код должен быть переписан путем сохранения оцененных результатов в их собственных переменных:

bool biconditional_with_function_rewritten()
{
bool a = fa();
bool b = fb();
return (a && b) || (!a && !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() && 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
2