Сегодня я начал экспериментировать с ветвлением, которое проверяет наличие двух логических значений. Я был почти уверен, что на каком-то уровне оптимизации они будут просто добавлены, а затем проверены, но это не относится к gcc и clang. Почему gcc не оптимизирует две проверки типа bool, заменяя их сложением и одной проверкой? Позвольте мне показать вам пример:
void test(bool a, bool b)
{
// Branch 1
if (a && b)
{
std::cout << "Branch 1";
}
// Branch 2
if (static_cast<int>(a) + static_cast<int>(b))
{
std::cout << "Branch 2";
}
}
Gcc (даже с максимальным уровнем оптимизации) генерирует следующий код для ветви 1:
test dil,dil
je 400794 <test(bool, bool)+0x14>
test sil,sil
jne 4007b0 <test(bool, bool)+0x30>
В то время как он генерирует следующий код для ветви 2:
movzx ebx,bl
movzx ebp,bpl
add ebx,ebp
jne 4007cf <test(bool, bool)+0x4f>
Разве две ветви (test + je) не должны быть медленнее, чем добавление и ветвь (add + jne)?
Изменить: на самом деле я имел в виду умножение, потому что в случае истинного и ложного (1 + 0) сложение дает истину (1), но умножение дает правильный результат (0).
3 ответа
Почему gcc не оптимизирует две проверки типа bool, заменяя их сложением и одной проверкой?
Предлагаемая оптимизация неверна. сложение не является подходящей заменой для оператора &&
, потому что первый будет оценивать как true
, когда хотя бы (но не оба) одно условие это true
.
Остается вопрос, как можно оптимизировать?
Стандарт C ++ гарантирует, что значения bool
преобразуются в int
с определенными значениями: false
преобразуется в 0
, а true
преобразуется в 1
. Следовательно, следующая конструкция является полностью юридическим эквивалентом (при условии, что и a
, и b
являются просто переменными bool
):
if (a & b) // OK, integral promotions take place (bool ---> int)
Предполагая, что компилятор всегда хранит значения bool
с одним и тем же внутренним представлением для true
(например, 0x1
) и false
(0x0
), условие можно напрямую преобразовать в x86 test
инструкция:
test sil, dil
Вот и самая сложная часть. По-видимому, компилятор GCC изменил свое поведение между основной веткой 4.6 и 4.7. Даже с явным приведением к int
он сохраняет два отдельных перехода. Состояние:
if (static_cast<int>(a) & static_cast<int>(b))
Генерирует код (GCC 6.2.0 с уровнем оптимизации -O3
):
test dil, dil
je .L1
test sil, sil
je .L1
С другой стороны, компиляторы ICC и MSVC 2015 выполняют «истинное» побитовое и:
movzx eax, BYTE PTR b$[rsp]
test al, BYTE PTR a$[rsp]
je SHORT $LN6@main
Что также относится к GCC до версии 4.7 (GCC 4.6.3 с -O3
):
movzx edi, dil
test esi, edi
test
. Кто-то должен отправить отчет о дефекте как для GCC, так и для Clang. Я не могу придумать ни одной причины, по которой было бы необходимо тестировать значения независимо, и это, конечно, не оптимально.
Вы забываете, что логические операторы выполняют оценку короткого замыкания.
Для логического и, если левая часть ложна, правая часть не оценивается.
На уровне абстрактной машины &&
заставляет второе выражение не вычисляться, если первое ложно. По правилу «как если бы» компилятор мог выбрать оценку второго выражения в любом случае - если он сможет доказать, что имеет определенное поведение (или неопределенное поведение не будет иметь значения) и никаких побочных эффектов; однако авторы компилятора ясно решили, что это не стоит того.
Если вы не хотите сокращать текст, &
может быть полезным (с комментарием).
a && b && c && d && e && f
сможет ничего не оценивать, кроме a
, если a
ложно, тогда как, как и в случае умножения, ему придется оценивать / умножать все результаты вместе.
a && b
не даст такого же результата, как a + b
во всех возможных случаях. Рассмотрим a = true
и b = false
.
a&&b
на a&b
раньше (см. -Fdump-tree-gimple). Затем серверная часть решает расширить (a&b)!=0
как 2 перехода, поскольку считает, что это в среднем более эффективно.
test sil, dil
+ je
. Как это могло быть медленнее, чем test sil, sil
+ je
+ test dil, dil
+ je
?
Похожие вопросы
Новые вопросы
c++
C++ — это язык программирования общего назначения. Изначально он разрабатывался как расширение C и имел аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде, который будет скомпилирован с помощью компилятора C++. Используйте тег версии для вопросов, связанных с конкретной стандартной версией [C++11], [C++14], [C++17], [C++20] или [C++23]. и т.д.
&&
, а||
. Кроме того, хотя я не уверен в текущем стандарте C ++, но раньше представление было0
дляfalse
и все остальное дляtrue
. Таким образом, у вас могут быть-1
и+1
для двух истинных значений, и их добавление не очень хорошо. Точно так же у вас могут быть1
и2
, и побитовыйand
для них не подходит.bool
могут быть толькоtrue
илиfalse
(я думаю , что это верно и для значений Cbool
). При преобразовании вint
, они должны преобразовать в 1 или 0 соответственно.bool
.