Есть ли разница в производительности между 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
Я знаю, что этот вопрос касается C, но мне было бы интересно узнать, могут ли браузеры выполнять эту оптимизацию для javascript.
Даже если бы компилятор не оптимизировал, вы вряд ли заметите разницу на любом современном процессоре.
Итак, «Нет» верно для одного компилятора, с которым вы тестировали.
@Andreas: Хороший вопрос. Раньше я писал компилятор и имел возможность протестировать этот код на многих процессорах, операционных системах и компиляторах. Единственный компилятор, который я обнаружил, который не оптимизировал случай i ++ (фактически, компилятор, который привлек мое профессиональное внимание к этому), был компилятором Software Toolworks C80 Уолта Билофски. Этот компилятор был для систем Intel 8080 CP / M. Можно с уверенностью сказать, что любой компилятор, который не включает эту оптимизацию, не предназначен для общего использования.
Кроме того, no проверяется только на целые числа, однако в вопросе не упоминается тип i. Тогда ваше обобщение в строке 1. Простите, -1.
Несмотря на то, что разница в производительности незначительна и во многих случаях оптимизирована, обратите внимание, что по-прежнему рекомендуется использовать ++i вместо i++. Нет абсолютно никаких причин не делать этого, и если ваше программное обеспечение когда-либо пройдет через цепочку инструментов, которая не оптимизирует его, ваше программное обеспечение будет более эффективным. Учитывая, что набрать ++i так же просто, как и i++, нет никакого оправдания тому, чтобы вообще не использовать ++i.
@Andreas: Большинство компиляторов оптимизируют это. Компилятору не имеет смысла использовать i ++, если вы нигде не используете его возвращаемое значение. Однако та же самая логика должна распространяться вплоть до программиста. Полагаться на оптимизацию - проблема сама по себе.
@monokrome Поскольку разработчики могут предоставить свою собственную реализацию для операторов префикса и постфикса на многих языках, это не всегда может быть приемлемым решением для компилятора без предварительного сравнения этих функций, что может быть нетривиальным.
@pickypg, хорошее замечание о различиях в языках. Совет monokrome звучит для C, и именно к этому относится этот конкретный вопрос.
@MarkHarrison Согласен. Я просто нашел свой путь сюда из другого вопроса и не хотел, чтобы люди читали это с предположением, которое может быть поднято на основе самого первого вопроса в этой ветке комментариев.
@pickypg Обратите внимание на вопросы здесь. Этот вопрос касается языка C, поэтому ваша точка зрения неверна в контексте обсуждения здесь. В C замечательно то, что он не поддерживает перегрузку операторов.
Конечно, я считаю, что для любого несовершенного компилятора C ответ - нет. Но поскольку многие программисты, занимающиеся программированием на C, также занимаются программированием на C++, использование ++ i по умолчанию является предпочтительным. Просто хорошая привычка, даже если она бессмысленна в C.
В ubuntu 14.04 64bits контрольные суммы файлов * .o (или файлов * .s) отличаются, даже если мы принудительно оптимизируем с параметром -O3. Конечно, это не доказывает, что одни и те же коды сборки разные.
Это все еще называется оптимизацией. По какой-то причине это делается даже с нулевыми флагами оптимизации, но это определенно не то же самое, что реализация оператора, который ничего не делает (например, i;), поскольку базовый код «должен» по крайней мере копировать регистр при использовании постфиксного оператора.
@monokrome очень хорошие моменты, и я с ними согласен. Особенно о том, как эта логика должна распространяться на программиста. Еще одна причина, по которой ++i должен использоваться по умолчанию, заключается в том, что легче увидеть, что что-то увеличивается. Возьмем, к примеру, reallylongvariablename++; против ++reallylongvariablename;. Намного легче увидеть, что первый увеличивается (например, когда он находится в отдельной строке). И это больше соответствует тому, как вы читаете это в уме, «приращение i» -> «++ i».
Я повторил описанный выше сценарий с циклом, используя size_t, и мои хеши другие. Запуск g ++ 5.3.1 в Ubuntu 16.04.
@StevenWalton сравните файлы .s и посмотрите, чем они отличаются
diff i++.s ++i.s показывает только имя файла и 1c1 вверху. Различие файлов cpp просто показывает разницу в i ++ и ++ i. Я провел небольшой тест и обнаружил, что ++ i работает немного быстрее. Но общее время работы составило около 3 секунд. После 10 прогонов каждый ++ i был средним 3,3153, а i ++ был средним 3,3248. Увидел такую же относительную разницу при перемещении петель на ~ 30 секунд. Совсем не большая разница.
Мой C немного заржавел, поэтому заранее прошу прощения. Поспешно, я могу понять результаты. Но меня смущает то, как оба файла попали в один и тот же хеш MD5. Может быть, цикл for работает одинаково, но разве следующие 2 строки кода не генерируют другую сборку?
myArray[i++] = "hello";
против
myArray[++i] = "hello";
Первый записывает значение в массив, затем увеличивает i. Второе приращение i затем записывает в массив. Я не специалист по сборке, но я просто не понимаю, как один и тот же исполняемый файл может быть сгенерирован этими двумя разными строками кода.
Всего два цента.
в вашем примере код будет другим, потому что вы используете значение. в примере, где они были одинаковыми, использовался только ++ для приращения, но не использовалось значение, возвращаемое ни тем, ни другим.
@Jason Z Оптимизация компилятора происходит до того, как сборка будет завершена, он увидит, что переменная i не используется где-либо еще в той же строке, поэтому сохранение ее значения было бы пустой тратой, она, вероятно, эффективно переворачивает ее на i ++. Но это только предположение. Я не могу дождаться, пока один из моих лекторов попытается сказать, что это быстрее, и я стану тем парнем, который исправляет теорию практическими доказательствами. Я уже почти чувствую враждебность ^ _ ^
Исправление: «компилятор увидит, что возвращаемое значение i ++ не используется, поэтому он перевернет его на ++ i». То, что вы написали, неверно еще и потому, что у вас не может быть i вместе с одним из i ++, i ++ в одной строке (операторе), этот результат не определен.
«Мой C немного заржавел, поэтому заранее прошу прощения». В вашем C все в порядке, но вы не прочитали полностью исходный вопрос: «Есть ли разница в производительности между i ++ и ++ i если полученное значение не используется?». Обратите внимание на курсив. В вашем примере используется «результат» i ++ / ++ i является, но в идиоматическом цикле for «результат» оператора pre / postincrement не используется, поэтому компилятор может делать то, что ему нравится.
Изменение foo[i++] на foo[++i] без изменения чего-либо еще, очевидно, изменило бы семантику программы, но на некоторых процессорах при использовании компилятора без логики оптимизации с подъемом цикла, увеличивая p и q один раз, а затем запуская цикл, который выполняется, например, *(p++)=*(q++); будет быстрее, чем использование цикла, который выполняет *(++pp)=*(++q);. Для очень плотных циклов на некоторых процессорах разница в скорости может быть значительной (более 10%), но это, вероятно, единственный случай в C, где постинкремент существенно быстрее, чем прединкремент.
В C компилятор обычно может оптимизировать их, чтобы они были такими же, если результат не используется.
Однако в C++ при использовании других типов, которые предоставляют свои собственные операторы ++, префиксная версия, вероятно, будет быстрее, чем постфиксная версия. Итак, если вам не нужна семантика постфикса, лучше использовать оператор префикса.
Взяв лист Скотта Мейерса, Более эффективный C++Правило 6: Различайте префиксную и постфиксную формы операций увеличения и уменьшения.
Префиксная версия всегда предпочтительнее постфиксной в отношении объектов, особенно в отношении итераторов.
Причина этого, если вы посмотрите на схему вызова операторов.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Глядя на этот пример, легко увидеть, как префиксный оператор всегда будет более эффективным, чем постфиксный. Из-за необходимости временного объекта в использовании постфикса.
Вот почему, когда вы видите примеры с использованием итераторов, они всегда используют префиксную версию.
Но, как вы указываете для int, фактически нет никакой разницы из-за возможной оптимизации компилятора.
Я думаю, что его вопрос был направлен на C, но в отношении C++ вы абсолютно правы, и, кроме того, люди C должны принять это, поскольку они могут использовать его и для C++. Я слишком часто вижу, как программисты на C используют постфиксный синтаксис ;-)
-1 Хотя это хороший совет для программистов на C++, он ни в малейшей степени не отвечает на вопрос, помеченный тегом C. В C абсолютно безразлично, используете ли вы префикс или постфикс.
Префикс @Lundin и postifx имеют значение в C согласно ответу Андреас. Вы не можете предположить, что компилятор будет оптимизировать, и это просто хорошая практика для любого языка для Всегда предпочитаю ++ i над i ++
@JProgrammer Они не имеют значения, согласно вашему искреннему ответу :) Если вы обнаружите, что они дают другой код, это либо потому, что вам не удалось включить оптимизацию, либо потому, что компилятор плохой. В любом случае, ваш ответ не по теме, так как вопрос был о C.
Лучше ответить, что ++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++ там, где это не влияет на результат.
В вопросе явно указано C, без ссылки на C++.
Этот (по общему признанию, старый) вопрос касался C, а не C++, но я полагаю, что также стоит упомянуть, что в C++, даже если класс реализует операторы после и до исправлений, они даже не обязательно связаны. Например, bar ++ может увеличивать один член данных, в то время как ++ bar может увеличивать другой член данных, и в этом случае у вас не будет возможности использовать ни один из них, поскольку семантика различна.
-1 Хотя это хороший совет для программистов на C++, он ни в малейшей степени не отвечает на вопрос, помеченный тегом C. В C абсолютно безразлично, используете ли вы префикс или постфикс.
@Pacerier Вопрос помечен тегами C и только C. Как вы думаете, почему они в этом не заинтересованы? Учитывая, как работает SO, не лучше ли было бы предположить, что они Только заинтересованы в C, а не в Java, C#, COBOL или любом другом языке, не относящемся к теме?
Вот еще одно наблюдение, если вас беспокоит микрооптимизация. Уменьшение циклов «возможно» может быть более эффективным, чем увеличение циклов (в зависимости от архитектуры набора команд, например, ARM), при условии:
for (i = 0; i < 100; i++)
В каждом цикле у вас будет по одной инструкции для каждого:
1 в i.i, чем 100.i меньше 100.В то время как убывающий цикл:
for (i = 100; i != 0; i--)
В цикле будет инструкция для каждого из:
i, установив флаг состояния регистра CPU.Z==0).Конечно, это работает только при уменьшении до нуля!
Вспомнил из Руководства разработчика системы ARM.
Хороший. Но разве при этом не будет меньше попаданий в кеш?
Есть старый странный трюк из книг по оптимизации кода, сочетающий преимущество ветки с нулевым тестом и увеличивающийся адрес. Вот пример на языке высокого уровня (вероятно, бесполезный, поскольку многие компиляторы достаточно умны, чтобы заменить его эффективным, но более распространенным кодом цикла меньше): int a [N]; для (i = -N; i; ++ i) a [N + i] + = 123;
@noop Не могли бы вы уточнить, на какие книги по оптимизации кода вы ссылаетесь? Я изо всех сил пытался найти хорошие.
Этот ответ вовсе не является ответом на вопрос.
@mezamorphic Для x86 моими источниками являются: руководства по оптимизации Intel, Agner Fog, Paul Hsieh, Michael Abrash.
@noop: не потеряет ли прирост эффективности в итерации цикла из-за дополнительной работы, требуемой a[N+i] вместо a[i].
@SoftwareMonkey и [N + i], и [i] генерируют одну инструкцию для архитектуры Intel, если N является постоянным. Кроме того, если N не является постоянным, вы просто заменяете [N + i] на a_end [i], где int * a_end = a + N. И поскольку значение a + N не изменяется внутри тела цикла, оно возможно, что оптимизатор выполнит эту замену за вас.
Из материала Эффективность против намерения Эндрю Кенига:
First, it is far from obvious that
++iis more efficient thani++, at least where integer variables are concerned.
И :
So the question one should be asking is not which of these two operations is faster, it is which of these two operations expresses more accurately what you are trying to accomplish. I submit that if you are not using the value of the expression, there is never a reason to use
i++instead of++i, because there is never a reason to copy the value of a variable, increment the variable, and then throw the copy away.
Итак, если полученное значение не используется, я бы использовал ++i. Но не потому, что он более эффективен, а потому, что правильно выражает мои намерения.
Не будем забывать, что и другие унарные операторы также являются префиксными. Я думаю, что ++ i - это «семантический» способ использования унарного оператора, в то время как i ++ существует для удовлетворения конкретной потребности (оценка перед добавлением).
Если полученное значение не используется, в семантике присутствует нет разницы: т.е. нет оснований для предпочтения той или иной конструкции.
Как читатель, я вижу разницу. Итак, как писатель, я покажу свое намерение, выбрав одно из них. У меня просто привычка - пытаться общаться с моими друзьями-программистами и товарищами по команде через код :)
Почему i++ создает копию вместо того, чтобы просто откладывать приращение до тех пор, пока выражение, ссылающееся на переменную, не будет завершено? Были ли старые компиляторы недостаточно умны для этого?
Легко придумывать причины, когда это не имеет большого значения, по сути, это потеря велосипеда. Я уверен, что кто-то может возразить, что i ++ предпочтительнее, потому что ++ i выглядит похожим на + i, а для начинающих ++ i может выглядеть только странно, но i ++ заставит их открыть главы об операторах.
Следуя совету Кенига, я кодирую i++ так же, как и i += n или i = i + n, то есть в форме цельглаголобъект, с операндом цель слева от оператора глагол. В случае i++ нет правого объект, но правило все еще применяется, сохраняя цель слева от оператора глагол.
Если вы пытаетесь увеличить i, то i ++ и ++ i правильно выражают ваше намерение. Нет причин предпочитать одно другому. Как читатель, я не вижу разницы, не путаются ли ваши коллеги, когда видят i ++ и думают, может быть, это опечатка и он не хотел увеличивать i?
Есть разница. Рассмотрим это: 1) int i = 0; 2) int x = i ++; 3) int y = ++ i; При семантическом чтении в строке 2 говорится: «Дайте мне значение i, а затем увеличьте его»; тогда как в строке 3 написано: «Увеличьте i, затем дайте мне его значение».
@Ifalin, мы говорим об автономных. Никто в здравом уме не использует i++ / ++i как часть заявления.
Если вы пытаетесь «увеличить i», гораздо естественнее читать «приращать i», чем читать «i увеличить». Так что даже игнорируя оптимизацию компилятора. Это оптимизирует читаемость.
Пожалуйста, не позволяйте вопросу «какой из них быстрее» быть решающим фактором, который выбрать. Скорее всего, вы никогда не будете так сильно заботиться, и, кроме того, время чтения программиста намного дороже, чем машинное время.
Используйте то, что имеет наибольший смысл для человека, читающего код.
Я считаю, что неправильно отдавать предпочтение нечетким улучшениям читабельности фактическому повышению эффективности и общей ясности намерений.
Мой термин «время чтения программиста» примерно аналогичен «ясности намерений». «Фактический прирост эффективности» часто неизмерим, достаточно близок к нулю, чтобы назвать его нулем. В случае OP, если код не был профилирован, чтобы обнаружить, что ++ i является узким местом, вопрос о том, какой из них быстрее, является пустой тратой времени и мыслительных единиц программиста.
Разница в удобочитаемости между ++ i и i ++ - это только вопрос личных предпочтений, но ++ i явно подразумевает более простую операцию, чем i ++, несмотря на то, что результат эквивалентен для тривиальных случаев и простых типов данных при использовании оптимизирующего компилятора. Поэтому ++ i для меня является победителем, когда специфические свойства постинкремента не нужны.
Вы говорите то, что говорю я. Более важно показать намерение и улучшить читаемость, чем беспокоиться об «эффективности».
Я все еще не могу согласиться. Если удобочитаемость стоит выше в вашем списке приоритетов, возможно, вы выбрали неправильный язык программирования. Основная цель C / C++ - написание эффективного кода.
Однако я всегда предпочитаю предварительное приращение ...
Я хотел указать, что даже в случае вызова функции operator ++ компилятор сможет оптимизировать временное, если функция будет встроена. Поскольку оператор ++ обычно короткий и часто реализуется в заголовке, он, вероятно, будет встроен.
Итак, для практических целей, вероятно, нет большой разницы между производительностью двух форм. Однако я всегда предпочитаю предварительное приращение, поскольку мне кажется, что лучше прямо выразить то, что я пытаюсь сказать, чем полагаться на оптимизатор, чтобы понять это.
Кроме того, меньшее количество действий для optmizer, вероятно, означает, что компилятор будет работать быстрее.
Ваша публикация специфична для C++, а вопрос касается C. В любом случае ваш ответ неверен: для пользовательских операторов постинкремента компилятор, как правило, нет может производить столь же эффективный код.
Он заявляет, что «если функция встроена», и это делает его рассуждения правильными.
Поскольку C не обеспечивает перегрузки оператора, вопрос до и после обработки в значительной степени неинтересен. Компилятор может оптимизировать неиспользуемый временный интервал, используя ту же логику, которая применяется к любому другому неиспользуемому, вычисляемому примитивно значению. См. Выбранный ответ для образца.
@Отметка Несмотря на то, что компилятору разрешено оптимизировать временную копию переменной (на основе стека), а gcc (в последних версиях) это делает, не означает, что компиляторы все всегда будут делать это.
Я только что протестировал его с компиляторами, которые мы используем в нашем текущем проекте, и 3 из 4 не оптимизируют его.
Никогда не предполагайте, что компилятор все делает правильно, особенно если возможно более быстрый, но никогда более медленный код так легко читается.
Если у вас нет действительно глупой реализации одного из операторов в вашем коде:
Всегда предпочитал ++ i над i ++.
Просто любопытно ... почему вы используете 4 разных компилятора C в одном проекте? Или в одной команде или в одной компании, если на то пошло?
При создании игр для консолей каждая платформа имеет собственный компилятор / набор инструментов. В идеальном мире мы могли бы использовать gcc / clang / llvm для всех целей, но в этом мире мы должны мириться с Microsoft, Intel, Metroworks, Sony и т. д.
Я могу представить ситуацию, когда постфикс медленнее, чем приращение префикса:
Представьте, что процессор с регистром 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. При увеличении префикса компилятор может просто увеличить значение и продолжить его использование, возможно, избегая его перезагрузки, поскольку желаемое значение уже находится в регистре после увеличения. Однако с постфиксным приращением компилятор должен иметь дело с двумя значениями: старым и увеличенным, что, как я показал выше, приводит к еще одному доступу к памяти.
Конечно, если значение приращения не используется, например, один оператор i++;, компилятор может (и делает) просто генерировать инструкцию приращения независимо от использования постфикса или префикса.
В качестве примечания я хотел бы упомянуть, что выражение, в котором есть b++, нельзя просто преобразовать в выражение с ++b без каких-либо дополнительных усилий (например, путем добавления - 1). Так что сравнение двух, если они являются частью какого-то выражения, на самом деле неверно. Часто, когда вы используете b++ внутри выражения, вы не можете использовать ++b, поэтому даже если бы ++b был потенциально более эффективным, это было бы просто неправильно. Исключение, конечно, если выражение требует этого (например, a = b++ + 1;, который можно изменить на a = ++b;).
Прежде всего: разница между i++ и ++i в C.
К деталям.
++i работает быстрее.В C++ ++i более эффективен, если и только если i - это какой-то объект с перегруженным оператором приращения.
Почему?
В ++i объект сначала увеличивается, а затем может быть передан как константная ссылка любой другой функции. Это невозможно, если выражение - foo(i++), потому что теперь необходимо выполнить приращение до вызова foo(), но старое значение необходимо передать в foo(). Следовательно, компилятор вынужден сделать копию i, прежде чем он выполнит оператор приращения на оригинале. Дополнительные вызовы конструктора / деструктора - плохая часть.
Как отмечалось выше, это не относится к основным типам.
i++май будет быстрееЕсли не нужно вызывать конструктор / деструктор, что всегда имеет место в C, ++i и i++ должны быть одинаково быстрыми, верно? Нет. Они практически одинаково быстры, но могут быть небольшие различия, которые большинство других респондентов ошиблись.
Как i++ может быть быстрее?
Дело в зависимостях данных. Если значение необходимо загрузить из памяти, необходимо выполнить с ним две последующие операции, увеличивая его на единицу и используя его. С ++i необходимо увеличить приращение перед, значение можно использовать. С i++ использование не зависит от приращения, и ЦП может выполнять операцию использования в параллели для операции приращения. Разница составляет не более одного цикла ЦП, поэтому ею можно пренебречь, но она есть. И все наоборот, чем многие ожидали.
По поводу вашего пункта 2: если ++i или i++ используется в другом выражении, изменение между ними изменяет семантику выражения, поэтому о любом возможном выигрыше / потере производительности не может быть и речи. Если они автономны, то есть результат операции не используется сразу, то любой достойный компилятор скомпилирует его в одно и то же, например инструкцию сборки INC.
@Shahbaz Совершенно верно, но не в этом дело. 1) Несмотря на то, что семантика различна, i++ и ++i могут использоваться взаимозаменяемо почти во всех возможных ситуациях, изменяя константы цикла на единицу, так что они почти эквивалентны в том, что они делают для программиста. 2) Несмотря на то, что обе компилируются в одну и ту же инструкцию, их выполнение отличается для ЦП. В случае i++ ЦП может вычислить приращение в параллели для какой-либо другой инструкции, которая использует то же значение (ЦП действительно это делают!), В то время как с ++i ЦП должен запланировать приращение другой инструкции после.
@Shahbaz В качестве примера: if (++foo == 7) bar(); и if (foo++ == 6) bar(); функционально эквивалентны. Тем не менее, второй может быть на один цикл быстрее, потому что сравнение и приращение могут вычисляться ЦП параллельно. Не то чтобы этот единственный цикл имел большое значение, но разница есть.
Хорошая точка зрения. Константы часто появляются (а также <, например, против <=) там, где обычно используется ++, поэтому преобразование между ними часто легко возможно.
Мне нравится пункт 2, но он применим только в том случае, если используется значение, верно? Вопрос гласит: «Если полученное значение не используется?», Поэтому это может сбивать с толку.
@jinawee Если значение ++i или i++ не используется, они полностью эквивалентны и должны компилироваться в один и тот же код. (Если i не является объектом класса, который имеет нестандартные, отдельные операторы приращения до и после).
Краткий ответ:
Между 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.Теперь вы, как программист на C, должны отличаться от людей, которые непоследовательно используют префикс в некоторых случаях и постфикс в других случаях, без каких-либо объяснений почему. Это говорит о том, что они не уверены в том, как работает C, или что у них неправильное знание языка. Это всегда плохой знак, он, в свою очередь, предполагает, что они принимают другие сомнительные решения в своей программе, основанные на суевериях или «религиозных догмах».
«Префикс ++ всегда быстрее» - одна из таких ложных догм, распространенных среди потенциальных программистов на C.
Никто не сказал: «Префикс ++ всегда быстрее». Это неправильно процитировано. Они сказали: «Postfix ++ быстрее никогда».
@Pacerier Я не цитирую какого-то конкретного человека, а просто распространяю неверное мнение.
@rbaleksandar C++ == c && ++ c! = c
Я прочитал здесь большую часть ответов и многие комментарии, и я не видел никаких ссылок на экземпляр один, о которых я мог бы подумать, где i++ более эффективен, чем ++i (и, возможно, на удивление, --iбыл более эффективен, чем i--. ). Это для компиляторов C для DEC PDP-11!
PDP-11 имел инструкции сборки для предварительного декремента регистра и постинкремента, но не наоборот. Инструкции позволяли использовать любой регистр «общего назначения» в качестве указателя стека. Поэтому, если вы использовали что-то вроде *(i++), его можно было бы скомпилировать в одну инструкцию по сборке, а *(++i) - нет.
Это, очевидно, очень эзотерический пример, но он обеспечивает исключение, когда постинкремент более эффективен (или я должен сказать был, поскольку в наши дни нет большого спроса на код PDP-11 C).
Очень эзотерически, но очень интересно!
@daShier. +1, хотя я не согласен, это не так уж эзотерично, по крайней мере, не должно быть. C был разработан совместно с Unix в AT&T Bell Labs в начале 70-х, когда PDP-11 был целевым процессором. В исходном коде Unix этой эпохи пост-инкремент «i ++» более распространен отчасти потому, что разработчики знали, когда присваивается значение «j = i ++» или используется в качестве индекса «a [i ++] = n», код будет немного быстрее (и меньше). Похоже, они привыкли использовать пост-инкремент, если не требовалось pre. Другие учились, читая свой код, и тоже переняли эту привычку.
68000 имеет ту же функцию, пост-инкремент и пре-декремент поддерживаются аппаратно (как режимы адресации), но не наоборот. Команда Motorola была вдохновлена DEC PDP-11.
@jimhark, да, я один из тех программистов PDP-11, которые перешли на 68000 и до сих пор используют --i и i++.
Связанный, тот же вопрос, но явно для С ++ - stackoverflow.com/questions/24901/…