Ускоряет ли перестановка условной оценки цикл? - PullRequest
11 голосов
/ 09 апреля 2009

Немного странно: мой друг недавно сказал мне, что переставляет этот пример for loop from:

for(int i = 0; i < constant; ++i) {
    // code...
}

до:

for(int i = 0; constant > i; ++i) {
    // code...
}

немного увеличит производительность в C ++. Я не понимаю, как сравнение постоянного значения с переменной происходит быстрее, чем наоборот, и некоторые элементарные тесты, которые я выполнял, не показали разницы в скорости между двумя реализациями. То же самое относится и к тестированию этого цикла Python while:

while i < constant:
    # code...
    i += 1

против

while constant > i:
    # code...
    i += 1

Я не прав? Моих простых тестов недостаточно, чтобы определить изменение скорости? Это правда о других языках? Или это просто новая лучшая практика?

Ответы [ 13 ]

45 голосов
/ 09 апреля 2009

Это больше относится к фольклору C ++, микрооптимизациям рук, которые когда-то работали над определенной версией конкретного компилятора и когда-либо передавались как своего рода знания, отличающие владельца от общего стада. Это мусор. Профилирование - это правда.

17 голосов
/ 09 апреля 2009

Вероятно, нет, но если это произойдет, компилятор, вероятно, в любом случае выполнит оптимизацию автоматически. Так что делайте так, чтобы ваш код был наиболее читабельным.

10 голосов
/ 09 апреля 2009

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

Профилировщик

Это только способ, которым вы можете когда-либо утверждать с любой властью, что один путь является или не быстрее, чем другой.

8 голосов
/ 09 апреля 2009

Приведенные вами примеры не должны иметь абсолютно никакого различия в производительности в C ++, и я сомневаюсь, что они также будут отличаться в Python.

Возможно, вы путаете это с другой оптимизацией:

for (int i = 0; i < variable; ++i)

// ...vs...

for (int i = variable; i ; --i)

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

Однако , большую часть времени компилятор может оптимизировать первый случай во второй (особенно если он видит, что переменная фактически является константой), и на некоторых комбинациях компилятор / архитектура могут генерироваться инструкции что делает первый метод более похожим на второй. Подобные вещи стоит попробовать только в том случае, если у вас узкий внутренний цикл, о котором говорит ваш профилировщик, это дорого, но в противном случае вы никогда не заметите разницу, даже если она есть.

5 голосов
/ 09 апреля 2009

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

while(bContinue && QueryStatusFromDatabase==1){
}  //while

будет намного быстрее, чем:

while(QueryStatusFromDatabase==1 && bContinue){
}  //while

Даже если они логически идентичны.

Это потому, что первый из них может быть остановлен, как только простое логическое значение будет FALSE - запрос должен выполняться только тогда, когда логическое значение TRUE, но второе будет всегда выполнять запрос.

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

Хуже всего, когда у вас есть функция как условие, и у этой функции есть побочные эффекты, которые тайно ожидаются другим местом в коде. Поэтому, когда вы проводите небольшую оптимизацию, побочные эффекты случаются только в некоторое время времени, и ваш код ломается странным образом. Но это немного касательно. Короткий ответ на ваш вопрос: «Иногда, но обычно это не имеет значения».

4 голосов
/ 09 апреля 2009

Хотя профилирование лучше, это не только способ.

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

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

3 голосов
/ 09 апреля 2009

Лучше не выходить из процесса оптимизации, подобной этой, которая даст вам незначительную выгоду (при условии, что - это - настройка).

2 голосов
/ 09 апреля 2009

Любой здравомыслящий компилятор будет реализовывать оба способа одинаково. Если один из них работает быстрее, чем другой в какой-либо архитектуре, компилятор оптимизирует его таким образом.

1 голос
/ 03 августа 2009

Сравнение с 0 очень быстрое, поэтому на самом деле это будет немного быстрее:

for (int i = constant; i > 0; --i)
{ 
  //yo
}

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

0 голосов
/ 01 августа 2009

Поставленная оптимизация только оптимизирует больше для данного компилятора (возможно). Абстрактно, он должен генерировать тот же код.

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

Например, i ++ может быть быстрее, чем i + 1. Зависит. В наивных процессорах равенство 0 намного быстрее, чем меньше. Если ваш компилятор / ЦП не поддерживает переупорядочение команд, вы можете обнаружить, что встраивание распределений в вычисления ускоряет ваш код. (некоторые вычисления могут привести к остановке конвейера) Но это то, что вам нужно будет конкретно определить для вашей комбинации компилятор / архитектура.

Честно говоря, я бы не потрудился выполнить этот уровень оптимизации, если бы мне абсолютно не требовалось каждый последний цикл от моего процессора. Традиционно, графические или научные вычисления - это то, где вам нужны такие вещи [*].

* Мне известна программа, которая после месяцев оптимизации и на современных машинах все еще может занять много месяцев для обработки данных. Время выполнения для одного набора данных находится в недельном диапазоне. Есть довольно много данных для использования ....

...