У нас есть вопрос, есть ли разница в производительности между i++
и ++i
в C ?
Какой ответ для C ++?
c++
performance
oop
post-increment
pre-increment
Марк Харрисон
источник
источник
Ответы:
[Резюме: используйте,
++i
если у вас нет конкретной причины использоватьi++
.]Для C ++ ответ немного сложнее.
Если
i
это простой тип (не экземпляр класса C ++), то ответ дан для C («Нет, разницы в производительности нет») имеет место, поскольку компилятор генерирует код.Однако, если
i
это экземпляр класса C ++, тоi++
и++i
выполняются вызовы одной изoperator++
функций. Вот стандартная пара этих функций:Поскольку компилятор не генерирует код, а просто вызывает
operator++
функцию, нет способа оптимизироватьtmp
переменную и связанный с ней конструктор копирования. Если конструктор копирования стоит дорого, это может оказать значительное влияние на производительность.источник
Да. Там есть.
Оператор ++ может быть или не быть определен как функция. Для примитивных типов (int, double, ...) операторы встроены, поэтому компилятор, вероятно, сможет оптимизировать ваш код. Но в случае объекта, который определяет оператор ++, все иначе.
Функция operator ++ (int) должна создавать копию. Это связано с тем, что postfix ++ должен возвращать значение, отличное от того, что он содержит: он должен хранить свое значение в переменной temp, увеличивать его значение и возвращать temp. В случае оператора ++ () с префиксом ++ нет необходимости создавать копию: объект может сам себя увеличивать, а затем просто возвращать себя.
Вот иллюстрация этого вопроса:
Каждый раз, когда вы вызываете operator ++ (int), вы должны создавать копию, и компилятор ничего не может с этим поделать. Когда предоставляется выбор, используйте оператор ++ (); Таким образом, вы не сохраняете копию. Это может быть важно в случае многих приращений (большой цикл?) И / или больших объектов.
источник
C t(*this); ++(*this); return t;
Во второй строке вы увеличиваете указатель this вправо, поэтому какt
обновляться, если вы увеличиваете это. Не были ли скопированы значения этогоt
?The operator++(int) function must create a copy.
нет это не так. Не больше копий, чемoperator++()
Вот пример для случая, когда операторы приращения находятся в разных единицах перевода. Компилятор с g ++ 4.5.
Проигнорируйте проблемы стиля пока
O (n) приращение
Тестовое задание
Результаты
Результаты (время в секундах) с g ++ 4.5 на виртуальной машине:
O (1) приращение
Тестовое задание
Давайте теперь возьмем следующий файл:
Это ничего не делает в приращении. Это моделирует случай, когда приращение имеет постоянную сложность.
Результаты
Результаты теперь сильно различаются:
Вывод
С точки зрения производительности
Если вам не нужно предыдущее значение, установите привычку использовать предварительное увеличение. Будьте последовательны даже со встроенными типами, вы привыкнете к этому и не рискуете понести ненужную потерю производительности, если вы когда-нибудь замените встроенный тип на пользовательский тип.
Семантический-накрест
i++
говоритincrement i, I am interested in the previous value, though
.++i
говоритincrement i, I am interested in the current value
илиincrement i, no interest in the previous value
. Опять же, вы привыкнете к этому, даже если вы не сейчас.Кнут.
Преждевременная оптимизация - корень всего зла. Как и преждевременная пессимизация.
источник
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
не обращайте внимания на фактическую структуру дерева (BSP, kd, Quadtree, Octree Grid и т. Д.). Такой итератор должен был бы поддерживать какое - то состояние, напримерparent node
,child node
,index
и тому подобное. В общем, моя позиция такова, даже если существует только несколько примеров ...Не совсем правильно говорить, что компилятор не может оптимизировать удаление временной переменной в случае постфикса. Быстрый тест с VC показывает, что он, по крайней мере, может сделать это в определенных случаях.
В следующем примере сгенерированный код идентичен, например, для префикса и постфикса:
Делаете ли вы ++ testFoo или testFoo ++, вы все равно получите тот же самый результирующий код. На самом деле, не считывая счет от пользователя, оптимизатор сводил все это к константе. Итак, это:
Результатом стало следующее:
Таким образом, хотя постфиксная версия может быть медленнее, вполне возможно, что оптимизатор будет достаточно хорош, чтобы избавиться от временной копии, если вы ее не используете.
источник
В Google C ++ Style Guide говорит:
источник
Я хотел бы отметить отличный пост Эндрю Кенига о Code Talk совсем недавно.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
В нашей компании мы также используем соглашение ++ iter для согласованности и производительности, где это применимо. Но Эндрю поднимает упущенные детали относительно намерения против производительности. Иногда мы хотим использовать iter ++ вместо ++ iter.
Итак, сначала определитесь с вашим намерением, и если pre или post не имеют значения, тогда переходите к pre, поскольку это принесет некоторую выгоду производительности, избегая создания дополнительного объекта и бросая его.
источник
@Ketan
Очевидно, что post и pre-increment имеют разную семантику, и я уверен, что все согласны с тем, что при использовании результата вы должны использовать соответствующий оператор. Я думаю, вопрос в том, что делать, когда результат отбрасывается (как в
for
циклах). Ответ на этот вопрос (ИМХО) заключается в том, что, поскольку соображения производительности в лучшем случае незначительны, вы должны делать то, что более естественно. Для меня++i
это более естественно, но мой опыт подсказывает мне, что я в меньшинстве, и использованиеi++
приведет к снижению затрат металла для большинства людей, читающих ваш код.Ведь именно поэтому язык не называется "
++C
". [*][*] Включить обязательное обсуждение
++C
более логичного имени.источник
Когда не используется возвращаемое значение, компилятор гарантированно не использует временный в случае ++ i . Не гарантируется, что будет быстрее, но гарантированно не будет медленнее.
При использовании возвращаемого значения i ++ позволяет процессору вставлять в конвейер как инкремент, так и левую сторону, поскольку они не зависят друг от друга. ++ Я могу остановить конвейер, потому что процессор не может запустить левую сторону до тех пор, пока операция предварительного приращения не будет извилистой до конца. Опять же, остановка конвейера не гарантируется, так как процессор может найти другие полезные вещи, чтобы застрять.
источник
Марк: Просто хотел бы отметить, что операторы ++ являются хорошими кандидатами для встраивания, и если компилятор решит это сделать, избыточная копия будет исключена в большинстве случаев. (например, типы POD, которые обычно являются итераторами.)
Тем не менее, в большинстве случаев все еще лучше использовать ++ iter. :-)
источник
Разница в производительности между
++i
иi++
будет более очевидной, когда вы будете думать об операторах как о функциях, возвращающих значения, и о том, как они реализованы. Чтобы было легче понять, что происходит, в следующих примерах кода будет использоваться,int
как если бы это былоstruct
.++i
увеличивает переменную, затем возвращает результат. Это может быть сделано на месте и с минимальным временем процессора, требующим во многих случаях только одной строки кода:Но то же самое нельзя сказать о
i++
.Постинкрементное,
i++
часто рассматривается как возвращение исходного значения перед приращением. Однако функция может вернуть результат только после его завершения . В результате возникает необходимость создать копию переменной, содержащей исходное значение, увеличить значение переменной, а затем вернуть копию, содержащую исходное значение:Когда функциональных различий между преинкрементом и постинкрементом нет, компилятор может выполнить оптимизацию так, чтобы между ними не было разницы в производительности. Однако, если используется составной тип данных, такой как
struct
илиclass
, конструктор копирования будет вызываться после инкремента, и эта оптимизация будет невозможна, если требуется глубокое копирование. Таким образом, предварительное увеличение обычно происходит быстрее и требует меньше памяти, чем последующее увеличение.источник
@Mark: я удалил свой предыдущий ответ, потому что это было немного перевернуто, и заслужил понижение для одного этого. Я на самом деле думаю, что это хороший вопрос в том смысле, что он спрашивает, что думают многие люди.
Обычный ответ таков: ++ я быстрее, чем i ++, и, без сомнения, так и есть, но главный вопрос в том, «когда тебя это волнует?».
Если доля процессорного времени, затрачиваемого на инкрементные итераторы, составляет менее 10%, вам может быть все равно.
Если доля процессорного времени, затрачиваемого на увеличение итераторов, превышает 10%, вы можете посмотреть, какие операторы выполняют эту итерацию. Посмотрите, можете ли вы просто увеличивать целые числа, а не использовать итераторы. Скорее всего, вы могли бы, и, хотя это может быть в некотором смысле менее желательно, шансы довольно хороши, вы сэкономите практически все время, проведенное в этих итераторах.
Я видел пример, когда увеличение итератора занимало более 90% времени. В этом случае переход к целочисленному приращению сокращает время выполнения по существу на эту величину. (т.е. лучше, чем 10-кратное ускорение)
источник
@wilhelmtell
Компилятор может исключить временный. Дословно из другой ветки:
Компилятору C ++ разрешено устранять временные эффекты на основе стека, даже если это изменяет поведение программы. Ссылка MSDN для VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
источник
Причина, по которой вы должны использовать ++ i даже на встроенных типах, в которых нет никакого преимущества в производительности, заключается в создании хорошей привычки для себя.
источник
И то, и другое так же быстро;) Если вы хотите, чтобы для процессора использовались одни и те же вычисления, различается только порядок их выполнения.
Например, следующий код:
Произведите следующую сборку:
Вы видите, что для a ++ и b ++ это мнемоника вкл, так что это та же самая операция;)
источник
Намеченный вопрос был о том, когда результат не используется (это ясно из вопроса для C). Кто-нибудь может это исправить, так как вопрос «вики сообщества»?
О преждевременной оптимизации часто цитируют Кнута. Вот так. но Дональд Кнут никогда не защитит с этим ужасным кодом, который вы можете видеть в эти дни. Вы когда-нибудь видели a = b + c среди Java Integer (не int)? Это составляет 3 конверсии в бокс / распаковку. Очень важно избегать подобных вещей. И бесполезно писать i ++ вместо ++ i - это та же ошибка. РЕДАКТИРОВАТЬ: Как хорошо говорит Френель в комментарии, это можно суммировать как «преждевременная оптимизация - это зло, так же как и преждевременная пессимизация».
Даже тот факт, что люди более привыкли к i ++, является печальным наследием C, вызванным концептуальной ошибкой K & R (если вы следуете аргументу намерения, это логичный вывод; и защищать K & R, потому что они K & R, не имеет смысла, они отлично, но они не так хороши, как разработчики языка: в дизайне C существует множество ошибок, от get () до strcpy (), до API strncpy () (он должен был иметь API strlcpy () с первого дня) ).
Кстати, я один из тех, кто недостаточно привык к C ++, чтобы найти ++, которую я раздражаю читать. Тем не менее, я использую это, так как я признаю, что это правильно.
источник
++i
больше раздражающего, чемi++
(на самом деле, я нашел это круче), но остальная часть вашего поста получает мое полное признание. Возможно, добавьте пункт «преждевременная оптимизация - это зло, равно как и преждевременная пессимизация»strncpy
служил цели в файловых системах, которые они использовали в то время; имя файла было 8-символьным буфером, и оно не должно заканчиваться нулем. Вы не можете винить их за то, что они не видели 40 лет в будущем языковой эволюции.strlcpy()
было оправдано тем, что оно еще не было изобретено.Пришло время предоставить людям драгоценные камни мудрости;) - есть простой трюк, чтобы заставить приращение постфикса C ++ вести себя почти так же, как приращение префикса (Придумал это для себя, но видел это также в коде других людей, так что я не один).
По сути, хитрость заключается в том, чтобы использовать вспомогательный класс для отсрочки приращения после возврата, и RAII приходит на помощь
Изобретен для некоторого тяжелого пользовательского кода итераторов, и он сокращает время выполнения. Стоимость префикса по сравнению с постфиксом теперь является одной ссылкой, и если это пользовательский оператор, выполняющий тяжелые перемещения, префикс и постфикс дают мне одинаковое время выполнения.
источник
++i
быстрее чемi++
потому, что он не возвращает старую копию значения.Это также более интуитивно понятно:
Этот пример C печатает «02» вместо «12», которое вы можете ожидать:
То же самое для C ++ :
источник