Title.ru

У меня есть такой код, и мне его трудно читать:

// code1
if( (expensiveOperation1() && otherOperation() && foo()) 
     || (expensiveOperation2() && bar() && baz()) {
  // do something
}

Я просто изменил его на следующее, чтобы было удобнее читать:

// code2
const bool expr1 = expensiveOperation1() && otherOperation() && foo();
const bool expr2 = expensiveOperation2() && bar() && baz();
if(expr1 || expr2){
   // one of the conditions met
}

Но стоит ли мне теперь беспокоиться об эффективности?

Я имею в виду, что code1 если первое конъюнктивное предложение выполнено, то он даже не потрудится взглянуть на второе, потому что уже ясно, что утверждение будет истинным.

Но в моем более удобочитаемом примере оба cond1 и cond2 должны быть вычислены. Или компилятор будет достаточно умен, чтобы заменить my code2 на, code1 если expr2 больше нигде не используется?

Ответов (7)

Решение

Я бы сказал, что этого не должно быть, поскольку они логически не эквивалентны, если какая-либо из функций имеет побочные эффекты.

Однако следующее будет эквивалентно, и у него будет преимущество, позволяющее давать описательные имена тестовым функциям, что делает код более самодокументированным:

// code3
inline bool combinedOp1()
{
    return expensiveOperation1() && otherOperation() && foo();
}

inline bool combinedOp2()
{
    return expensiveOperation2() && bar() && baz();
}

А потом назовите его так:

if (combinedOp1() || combinedOp2())
{
    // do something
}

Ответ на этот вопрос, конечно, зависит от компилятора. Окончательный способ проверки - посмотреть на сборку, созданную компилятором для этой функции. У большинства (всех?) Компиляторов есть способ сделать это, например, gcc есть такая -S возможность. Если по какой-то странной причине ваш не работает, большинство отладчиков могут показать вам дизассемблирование функции, или есть другие инструменты для этого.

Самые популярные ответы здесь - это ответы на вопросы «не следует» и «может быть»! Да ладно, это не окончательный ответ!

Если вы хотите знать, оптимизирует ли ваш компилятор этот крошечный фрагмент кода, скомпилируйте свой код с флагом «показать вывод сборки». В GCC этот флаг - "-S". Затем посмотрите на выходную сборку, и она ТОЧНО на 100% покажет вам, что компилируется, а что нет.

Затем вы можете сравнить свой первый фрагмент кода с фрагментом кода из «оттуда» и быстро попробовать многочисленные изменения кода, пока не найдете то, которое компилятор оптимизирует лучше всего (т. Е. С наименьшими циклами).

Это звучит сложно и пугающе смотреть на вывод asm, но на самом деле это занимает всего около 5 минут. Я сделал здесь пример: Каков самый быстрый способ поменять местами значения в C?

Что ж, компилятор в целом не будет переупорядочивать && и ||, если у условий есть побочные эффекты. несколько очень умных компиляторов могут статически проверить свою независимость, но это будет редкостью.

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

Может быть, но почему бы просто не включить в ваш второй чек первый?

// code3
bool expr = expensiveOperation1() && otherOperation() && foo();
expr = expr || (expensiveOperation2() && bar() && baz());
if(expr){
   // one of the conditions met
}

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

Хорошие ответы.

Я бы только добавил, что мне не нравится, что компиляторы настолько агрессивны в оптимизации, чтобы переупорядочивать мой код.

Я просто хочу, чтобы компилятор делал то, что ему велят.

Если он может перехитрить меня, он может перехитрить и себя.

Компилятор может оптимизировать, если знает, что функции в cond2 (RoadOperation2 (), bar () и baz ()) являются чистыми (т.е. не имеют побочных эффектов). Если они чистые, самый простой способ убедиться, что компилятор знает об этом, - сделать их встроенными функциями.

Это возможно , что компилятор может сказать , даже если вы этого не сделаете, но очень маловероятно , так как expensiveOperation2 () , вероятно , делает довольно много работы.

FWIW, если эти функции являются чистыми, вам, вероятно, следует переупорядочить их, чтобы bar () и baz () выполнялись перед дорогоOperation2 () (и то же самое для порядка в cond1).