Существует ли разница в производительности между i++
и ++i
если результирующее значение не используется?
Резюме: Нет.
i++
потенциально может быть медленнее, чем ++i
, так как старое значение i
может потребоваться сохранить для последующего использования, но на практике все современные компиляторы оптимизируют это.
Мы можем продемонстрировать это, посмотрев код этой функции, как с ++i
и с i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
Файлы одинаковы, кроме ++i
и i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Мы скомпилируем их, а также получим сгенерированный ассемблер:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
И мы видим, что и сгенерированный объект, и файлы ассемблера совпадают.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
От эффективности и намерения Эндрю Кенига:
Во-первых, далеко не очевидно, что
++i
более эффективен, чемi++
, по крайней мере, где целые переменные.
А также:
Поэтому вопрос, который нужно задать, заключается не в том, какая из этих двух операций выполняется быстрее, а в какой из этих двух операций более точно выражается то, что вы пытаетесь выполнить. Я утверждаю, что если вы не используете значение выражения, никогда не будет причины использовать
i++
вместо++i
, потому что никогда не бывает причины копировать значение переменной, увеличивать эту переменную, а затем выбросьте копию.
Итак, если результирующее значение не используется, я бы использовал ++i
. Но не потому, что он более эффективен: потому что он правильно формулирует мое намерение.
Лучший ответ заключается в том, что ++i
будет иногда быстрее, но не медленнее.
Все, кажется, предполагают, что i
является обычным встроенным типом, таким как int
. В этом случае не будет заметной разницы.
Однако, если i
является сложным типом, вы можете найти измеримую разницу. Для i++
вы должны сделать копию своего класса, прежде чем увеличивать его. В зависимости от того, что связано с копией, оно действительно может быть медленнее, поскольку с помощью ++it
вы можете просто вернуть окончательное значение.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
Другое отличие состоит в том, что с ++i
вас есть возможность вернуть ссылку вместо значения. Опять же, в зависимости от того, что участвует в создании копии вашего объекта, это может быть медленнее.
Реальным примером того, где это может произойти, будет использование итераторов. Копирование итератора вряд ли будет бутылочной горловиной в вашем приложении, но все же хорошей практикой является привычка использовать ++i
вместо i++
где результат не i++
.
Здесь дополнительное наблюдение, если вы беспокоитесь о микро оптимизации. Сокращение циклов может быть "возможно" более эффективным, чем приращение циклов (в зависимости от архитектуры набора команд, например ARM), учитывая:
for (i = 0; i < 100; i++)
На каждом цикле вы будете иметь по одной инструкции для:
1
в i
.i
меньше 100
.i
меньше 100
.В то время как декрементирующий цикл:
for (i = 100; i != 0; i--)
Цикл будет иметь инструкцию для каждого из:
i
, установив флаг состояния ЦП.Z==0
).Конечно, это работает только при уменьшении до нуля!
Помните о руководстве разработчика системы ARM.
Получение листа от Скотта Мейерса, более эффективное c++ Пункт 6: Различать префиксные и постфиксные формы операций приращения и декремента.
Префиксная версия всегда предпочтительнее постфикса относительно объектов, особенно в отношении итераторов.
Причина этого, если вы посмотрите на шаблон вызова операторов.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Рассматривая этот пример, легко увидеть, как оператор префикса всегда будет более эффективным, чем постфикс. Из-за необходимости использования временного объекта при использовании постфикса.
Вот почему, когда вы видите примеры с использованием итераторов, они всегда используют префиксную версию.
Но поскольку вы указываете на int, нет никакой разницы из-за оптимизации компилятора, которая может иметь место.
Короткий ответ:
Нет никакой разницы между i++
и ++i
с точки зрения скорости. Хороший компилятор не должен генерировать другой код в двух случаях.
Длительный ответ:
То, что каждый другой ответ не упоминает, заключается в том, что разница между ++i
и i++
имеет смысл только в пределах найденного выражения.
В случае for(i=0; i<n; i++)
, i++
является единственным в своем собственном выражении: перед i++
есть точка последовательности, а после него - одна. Таким образом, только генерируемый машинный код - "увеличить i
на 1
", и он четко определен, как это упорядочивается по отношению к остальной части программы. Поэтому, если вы измените его на префикс ++
, это ни в коем случае не имеет значения, вы все равно просто получите машинный код "увеличить i
на 1
".
Различия между ++i
и i++
значение только в выражениях, таких как array[i++] = x;
против array[++i] = x;
, Некоторые могут возразить и сказать, что постфикс будет медленнее в таких операциях, потому что регистр, в котором i
проживаю, должен быть перезагружен позже. Но тогда обратите внимание, что компилятор может свободно заказывать ваши инструкции любым способом, если это не "нарушает поведение абстрактной машины", как называет его стандарт C.
Поэтому, хотя вы можете предположить, что array[i++] = x;
преобразуется в машинный код как:
i
в регистре A.i
в регистре A//неэффективно, потому что дополнительная инструкция здесь, мы уже сделали это один раз.i
.компилятор может также повысить эффективность кода, например:
i
в регистре A.i
. Просто потому, что вы, как программист С, обучены думать, что постфикс ++
происходит в конце, машинный код не нужно заказывать таким образом.
Таким образом, нет никакой разницы между префиксным и постфиксным ++
в C. Теперь то, что вы, как программист С, должно быть различным, - это люди, которые в некоторых случаях непоследовательно используют префикс и постфикс в других случаях, без каких-либо причин. Это говорит о том, что они не уверены в том, как работает C или что у них неправильное знание языка. Это всегда плохой знак, он, в свою очередь, предполагает, что они делают другие сомнительные решения в своей программе, основанные на суевериях или "религиозных догмах".
"Префикс ++
всегда быстрее" - это действительно одна из таких ложных догм, которая распространена среди потенциальных программистов на С.
Пожалуйста, не позволяйте вопросу о том, "какой из них быстрее", является решающим фактором для использования. Скорее всего, вы никогда не будете так заботиться, и, кроме того, время чтения программиста намного дороже машинного времени.
Используйте то, что имеет смысл для человека, читающего код.
Прежде всего: разница между i++
и ++i
нечетна в C.
К деталям.
++i
быстрее В C++, ++i
более эффективен, если i
- это какой-то объект с перегруженным оператором приращения.
Зачем?
В ++i
объект сначала увеличивается, а затем может передаваться как константная ссылка на любую другую функцию. Это невозможно, если выражение foo(i++)
потому что теперь приращение нужно выполнить до foo()
, но старое значение нужно передать foo()
. Следовательно, компилятор вынужден сделать копию i
до того, как он выполнит оператор инкремента в оригинале. Дополнительные вызовы конструктора/деструктора являются плохой частью.
Как отмечено выше, это не относится к фундаментальным типам.
i++
может быть быстрее Если не нужно вызывать конструктор/деструктор, что всегда имеет место в C, ++i
и i++
должны быть одинаково быстрыми, верно? Нет. Они практически одинаково быстры, но могут быть небольшие различия, которые большинство других ответчиков ошибались.
Как i++
может быть быстрее?
Точка - это зависимости данных. Если значение нужно загрузить из памяти, необходимо выполнить две последующие операции с ним, увеличить его и использовать. С помощью ++i
необходимо выполнить ++i
до того, как значение будет использоваться. С помощью i++
использование не зависит от приращения, а ЦП может выполнять операцию использования параллельно операции инкремента. Разница составляет не более одного цикла процессора, поэтому она действительно небрежна, но она есть. И наоборот, многие ожидали.
++i
или i++
используется в другом выражении, переключение между ними изменяет семантику выражения, поэтому любой возможный выигрыш / потеря производительности не обсуждается. Если они автономны, т. Е. Результат операции не используется сразу, то любой приличный компилятор скомпилирует его так же, например инструкцию сборки INC
.
i++
и ++i
можно использовать взаимозаменяемо практически в любой возможной ситуации, регулируя константы цикла на единицу, поэтому они почти эквивалентны тем, что они делают для программиста. 2) Несмотря на то, что оба компилируют одну и ту же инструкцию, их выполнение для ЦП различно. В случае i++
ЦП может вычислять приращение параллельно с какой-либо другой инструкцией, которая использует то же значение (ЦП действительно делают это!), В то время как с ++i
ЦП должен планировать другую инструкцию после приращения.
@Mark Несмотря на то, что компилятору разрешено оптимизировать временную копию переменной (на основе стека) и gcc (в последних версиях), это не значит, что все компиляторы всегда будут делать это.
Я просто тестировал его с помощью компиляторов, которые мы используем в нашем текущем проекте, и 3 из 4 не оптимизируют его.
Никогда не предполагайте, что компилятор правильно это понимает, особенно если возможно более быстрый, но не более медленный код.
Если у вас нет действительно глупой реализации одного из операторов в вашем коде:
Alwas предпочитает ++i по сравнению с i++.
Я могу думать о ситуации, когда postfix медленнее, чем приращение приставки:
Представьте, что процессор с регистром A
используется в качестве аккумулятора, и он единственный регистр, используемый во многих инструкциях (некоторые небольшие микроконтроллеры на самом деле похожи на это).
Теперь представьте следующую программу и их перевод в гипотетическую сборку:
Приращение приставки:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Приращение притчи:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
Обратите внимание, как значение b
было принудительно перезагружено. С приращением префикса компилятор может просто увеличить значение и продолжать использовать его, возможно, не перезагружать его, так как желаемое значение уже находится в регистре после приращения. Тем не менее, с приращением postfix, компилятор должен иметь дело с двумя значениями: один - старый и один - с добавленным значением, которое, как показано выше, приводит к еще одному доступу к памяти.
Конечно, если значение приращения не используется, например, одно i++;
, компилятор может (и делает) просто сгенерировать инструкцию приращения независимо от использования постфикса или префикса.
В качестве примечания я хотел бы упомянуть, что выражение, в котором есть b++
не может быть просто преобразовано в одно с ++b
без каких-либо дополнительных усилий (например, добавив a- - 1
). Поэтому сравнение двух, если они являются частью некоторого выражения, действительно недействительно. Часто, когда вы используете b++
внутри выражения, вы не можете использовать ++b
, поэтому, даже если ++b
потенциально более эффективен, это было бы просто неправильно. Исключение, конечно, если выражение попросит его (например, a = b++ + 1;
который можно изменить на a = ++b;
).
В C компилятор обычно может оптимизировать их, чтобы они были одинаковыми, если результат не используется.
Однако в C++ при использовании других типов, которые предоставляют свои собственные операторы ++, префиксная версия, вероятно, будет быстрее, чем версия postfix. Итак, если вам не нужна семантика postfix, лучше использовать префиксный оператор.
Я всегда предпочитаю pre-increment, однако...
Я хотел бы указать на то, что даже в случае вызова функции operator++ компилятор сможет оптимизировать временное, если функция становится встроенной. Так как operator++ обычно короток и часто реализуется в заголовке, он, скорее всего, будет встроен.
Таким образом, для практических целей, вероятно, не очень важна разница между производительностью двух форм. Тем не менее, я всегда предпочитаю предварительный прирост, так как кажется, что лучше прямо выразить то, что я пытаюсь сказать, вместо того, чтобы полагаться на оптимизатора, чтобы понять это.
Кроме того, предоставление optmizer менее вероятно, означает, что компилятор работает быстрее.
Мой C немного ржавый, поэтому я заранее извиняюсь. Скорость, я могу понять результаты. Но я смущен тем, как оба файла выходили в один и тот же MD5-хэш. Может быть, цикл for работает одинаково, но не будут ли следующие две строки кода генерировать другую сборку?
myArray[i++] = "hello";
против
myArray[++i] = "hello";
Первый записывает значение в массив, а затем увеличивает i. Затем второй приращение я записывается в массив. Я не эксперт по сборке, но я просто не вижу, как будет генерироваться один и тот же исполняемый файл этими двумя разными строками кода.
Только мои два цента.