Это не имеет ничего общего с подсчетом вверх или вниз . Что может быть быстрее, так это счет к нулю . Ответ Михаэля показывает, почему - x86 дает вам сравнение с нулем как неявный побочный эффект многих инструкций, поэтому после настройки счетчика вы просто переходите на основе результата вместо того, чтобы выполнять явное сравнение. (Может быть, другие архитектуры тоже так делают; я не знаю.)
Компиляторы Borland Pascal славятся выполнением этой оптимизации. Компилятор преобразует этот код:
for i := x to y do
foo(i);
во внутреннее представление, более похожее на это:
tmp := Succ(y - x);
i := x;
while tmp > 0 do begin
foo(i);
Inc(i);
Dec(tmp);
end;
(я говорю печально не потому, что оптимизация влияет на результат цикла, а потому, что отладчик неправильно отображает переменную счетчика. Когда программист проверяет i
, отладчик может вместо этого отображать значение tmp
, не вызывая конец путаницы и паники для программистов, которые думают, что их циклы работают задом наперед.)
Идея состоит в том, что даже с дополнительной инструкцией Inc
или Dec
это все еще чистый выигрыш, с точки зрения времени выполнения, по сравнению с выполнением явного сравнения. Можете ли вы на самом деле заметить , что разница обсуждается.
Но обратите внимание, что преобразование - это то, что компилятор сделает автоматически , в зависимости от того, посчитал ли это преобразование целесообразным. Компилятор обычно лучше оптимизирует код, чем вы, поэтому не тратьте слишком много усилий на его борьбу.
В любом случае, вы спрашивали о C ++, а не о Pascal. Циклы «for» в C ++ не так легко применить эту оптимизацию, как в циклах «for» в Pascal, потому что границы циклов Pascal всегда полностью вычисляются перед выполнением цикла, тогда как циклы C ++ иногда зависят от условия остановки и цикла содержание. Компиляторам C ++ необходимо провести некоторый статический анализ, чтобы определить, может ли какой-либо конкретный цикл соответствовать требованиям для того типа преобразования, для которого циклы Паскаля квалифицируются безоговорочно. Если компилятор C ++ выполняет анализ, он может выполнить аналогичное преобразование.
Ничто не мешает вам писать свои циклы таким образом:
for (unsigned i = 0, tmp = domain; tmp > 0; ++i, --tmp)
array[i] = do stuff
Выполнение этого может заставить ваш код работать быстрее. Как я уже говорил, вы, вероятно, не заметите. Большая плата, которую вы платите за ручную организацию таких циклов, состоит в том, что ваш код больше не следует установленным идиомам. Ваш цикл является совершенно обычным циклом «for», но он больше не выглядит как один - он имеет две переменные, они считают в противоположных направлениях, и одна из них даже не используется в цикле тело - так что любой, кто читает ваш код (включая вас, неделю, месяц или год, когда вы забыли «оптимизацию», на которую вы надеялись), должен будет приложить дополнительные усилия, чтобы доказать себе, что петля действительно замаскированная обычная петля.
(Вы заметили, что в моем коде выше использовались переменные без знака без опасности переноса на ноль? Использование двух отдельных переменных позволяет это сделать.)
Три вещи, которые нужно отнять от всего этого:
- Пусть оптимизатор сделает свою работу; в целом это лучше, чем ты.
- Сделайте так, чтобы обычный код выглядел обычным, чтобы специальному коду не приходилось конкурировать, чтобы привлечь внимание людей, проверяющих, отлаживающих или обслуживающих его.
- Не делайте ничего фантастического во имя производительности, пока тестирование и профилирование не покажут это необходимым.