В C ++ логические операторы &&
, ||
, !
есть, что соответствует соединению , дизъюнкция , отрицание , соответственно.
Но я заметил операторы сравнения ==
, !=
, <
, >
, <=
, >=
может также использоваться для логических значений! При условии P
а также Q
являются логическими значениями:
P == Q
является двунаправленным ,
P != Q
это эксклюзивный дизъюнкция ,
P < Q
обратное неявное ,
P > Q
это не подтекст ,
P <= Q
это подразумевается ,
А также P >= Q
обратное значение .
Мои вопросы:
Будет ли программа работать лучше с помощью этого трюка?
Есть ли пример кода, использующего этот трюк (на любом языке)?
Повысит ли производительность этот трюк?
На любом процессоре, где это было бы полезно, когда 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";
Это может быть написано с ^
, Что легче читать, это вопрос мнения.
На самом деле я думаю, что это может сделать код быстрее. Вот код для первой функции:
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