У нас вопрос есть ли разница в производительности между i++ и ++iв C?
Каков ответ на C++?
Есть ли разница в производительности между использованием C++ и ++ C?
Статья: Разумно ли использовать префиксный оператор приращения ++ it вместо постфиксного оператора it ++ для итераторов? - viva64.com/en/b/0093





[Краткое содержание: используйте ++i, если у вас нет особой причины использовать i++.]
Для C++ ответ немного сложнее.
Если i является простым типом (не экземпляром класса C++), затем ответ на C («Нет разницы в производительности») сохраняется, поскольку компилятор генерирует код.
Однако, если i является экземпляром класса C++, то i++ и ++i вызывают одну из функций operator++. Вот стандартная пара этих функций:
Foo& Foo::operator++() // called for ++i
{
this->data += 1;
return *this;
}
Foo Foo::operator++(int ignored_dummy_value) // called for i++
{
Foo tmp(*this); // variable "tmp" cannot be optimized away by the compiler
++(*this);
return tmp;
}
Поскольку компилятор не генерирует код, а просто вызывает функцию operator++, невозможно оптимизировать переменную tmp и связанный с ней конструктор копирования. Если конструктор копирования стоит дорого, это может существенно повлиять на производительность.
Компилятор может избежать второй копии, которая вернет tmp, выделив tmp в вызывающей программе через NRVO, как упоминается в другом комментарии.
i ++ на одну инструкцию процессора медленнее
Не может ли компилятор полностью избежать этого, если оператор ++ встроен?
Да, если оператор ++ встроен и tmp никогда не используется, его можно удалить, если конструктор или деструктор объекта tmp не имеет побочных эффектов.
Отметьте: «this-> data» находится в одном примере, а «this-> mydata» - в другом ... вы можете это исправить?
Поведение C++ не так уж отличается от C. Если вы думаете о назначении памяти или регистров, то C++ показывает, что компилятор C должен делать за кулисами. Также многие компиляторы C++ будут использовать operator ++ (), если не определен operator ++ (int) (и выдают предупреждение).
@kriss: разница между C и C++ заключается в том, что в C у вас есть гарантия, что оператор будет встроен, и в этот момент приличный оптимизатор сможет удалить разницу; вместо этого в C++ вы не можете предполагать встраивание - не всегда.
Я бы +1, ЕСЛИ в ответе упоминалось что-то о классах, которые содержат указатели (автоматические, интеллектуальные или примитивные) на динамически выделяемую (кучу) память, где конструктор копирования обязательно выполняет глубокие копии. В таких случаях аргумента нет, ++ i, возможно, на порядок эффективнее, чем i ++. Ключевым моментом для них является выработка привычки использовать преинкремент всякий раз, когда семантика постинкремента на самом деле не требуется вашему алгоритму, и тогда вы будете иметь привычку писать код, который по своей природе обеспечивает большую эффективность, независимо от того, как ну, ваш компилятор может оптимизировать.
Для тех, кто может быть сбит с толку фиктивным аргументом в operator++(int), он просто используется, чтобы различать две формы. Какая-то неряшливая, если вы спросите меня.
Кто-нибудь может прокомментировать эту тему, если это std::atomic<int> или аналогичный?
Я не понимаю, как в C вы можете гарантировать встраивание, а в C++ - нет?
@rubenvb: В C нет такой вещи, как перегрузка оператора, поэтому ++ (до или после) никогда не является вызовом функции; дело не столько в том, что встраивание гарантировано, сколько в том, что в первую очередь нет ничего, что предположительно "не соответствовало бы". Обе версии ++ делают одно и то же: увеличивают числовой тип или тип указателя. pre- vs. post- просто определяет, будет ли это сделано до или после того, как значение будет использовано в выражении, которое его содержит, но копирование не требуется; для переменной всегда существует одно согласованное значение с приращением до или после точки использования.
C++ не может дать эту гарантию, потому что перегрузка оператора означает, что в игру вступает семантика вызова функций; разница между ++x и x++ заключается в том, какая функция вызывается (operator++() или operator++(int)), а не в когда, которая вызывается. Если бы в стандарте C++ говорилось, что он определяет только operator++(), и просто говорилось, что он будет вызываться до или после выражения, в котором он был найден в зависимости от порядка, C++ будет соответствовать поведению C (потому что вы использовали бы только не копирующее приращение функция с синхронизацией, контролируемой компилятором), но это не так, поэтому вы можете избежать копирования только при встраивании.
@ PedroLamarão: для std::atomic (с целочисленным типом и типом указателя) обе перегрузки инкремента возвращаются по значению, а не по ссылке (обычно до ref), но они возвращают неатомарное значение, поэтому «копия» бесплатна. Скорее всего, оба несут одинаковые затраты; функционально это одна и та же операция (fetch_add, который возвращает исходное значение), где предварительное приращение возвращает результат fetch_add плюс 1, а постинкремент возвращает результат fetch_add напрямую. По сравнению с накладными расходами даже на поддерживаемые ЦП атомарные добавления, неатомарное приращение результата fetch_add будет потеряно в шуме.
Да. Есть.
Оператор ++ может быть определен как функция, а может и не быть. Для примитивных типов (int, double, ...) операторы встроены, поэтому компилятор, вероятно, сможет оптимизировать ваш код. Но в случае объекта, который определяет оператор ++, все обстоит иначе.
Функция operator ++ (int) должна создавать копию. Это потому, что ожидается, что postfix ++ вернет значение, отличное от того, которое он содержит: он должен хранить свое значение в переменной temp, увеличивать его значение и возвращать temp. В случае оператора ++ (), префикса ++, нет необходимости создавать копию: объект может увеличивать себя, а затем просто возвращать себя.
Вот иллюстрация к делу:
struct C
{
C& operator++(); // prefix
C operator++(int); // postfix
private:
int i_;
};
C& C::operator++()
{
++i_;
return *this; // self, no copy created
}
C C::operator++(int ignored_dummy_value)
{
C t(*this);
++(*this);
return t; // return a copy
}
Каждый раз, когда вы вызываете operator ++ (int), вы должны создавать копию, и компилятор ничего не может с этим поделать. Если у вас есть выбор, используйте operator ++ (); таким образом вы не сохраняете копию. Это может быть значительным в случае большого количества приращений (большой цикл?) И / или больших объектов.
«Оператор предварительного приращения вводит зависимость данных в коде: ЦП должен дождаться завершения операции приращения, прежде чем его значение можно будет использовать в выражении. В ЦП с глубоким конвейером это приводит к задержке. для оператора приращения поста ". (Архитектура игрового движка (2-е издание)) Итак, если копия пост-инкремента не требует больших вычислительных ресурсов, она все равно может превзойти пре-инкремент.
В постфиксном коде, как это работает? C t(*this); ++(*this); return t; Во второй строке вы увеличиваете указатель this вправо, так как же обновляется t, если вы увеличиваете это значение. Разве значения этого уже не скопированы в t?
The operator++(int) function must create a copy. нет, это не так. Не больше копий, чем operator++()
Не совсем правильно сказать, что компилятор не может оптимизировать копию временной переменной в случае постфикса. Быстрый тест с VC показывает, что он, по крайней мере, может это сделать в определенных случаях.
В следующем примере сгенерированный код идентичен для префикса и постфикса, например:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
Независимо от того, выполняете ли вы ++ testFoo или testFoo ++, вы все равно получите тот же результирующий код. Фактически, не считывая счетчик от пользователя, оптимизатор все это преобразовал в константу. Итак, это:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
В результате получилось следующее:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
Таким образом, хотя версия с постфиксом, безусловно, может быть медленнее, вполне может быть, что оптимизатор будет достаточно хорош, чтобы избавиться от временной копии, если вы ее не используете.
Вы забыли отметить важный момент, что здесь все встроено. Если определения операторов недоступны, нельзя избежать копирования, сделанного в автономном коде; с встраиванием optim совершенно очевидно, поэтому любой компилятор сделает это.
Причина, по которой вам следует использовать ++ i даже во встроенных типах, где нет преимущества в производительности, заключается в том, чтобы создать себе хорошую привычку.
Извините, но это меня беспокоит. Кто сказал, что это «хорошая привычка», когда это почти не имеет значения? Если люди хотят сделать это частью своей дисциплины, это нормально, но давайте отделим важные причины от вопросов личного вкуса.
@MikeDunlavey, хорошо, какую сторону вы обычно используете, когда это не имеет значения? xD то либо одно, либо другое не так ли! сообщение ++ (если вы используете его с общим смыслом. обновите его, верните старое) полностью уступает ++ pre (обновите его, верните), нет никаких причин, по которым вы хотели бы иметь меньшую производительность. в случае, если вы захотите обновить его позже, тогда программист даже не сделает post ++. не тратить время на копирование, когда оно у нас уже есть. обновите его после того, как мы его используем. затем у компиляторов есть здравый смысл, который вы хотели.
@Puddle: Когда я слышу это: «Нет никаких причин, по которым вы хотели бы иметь меньшую производительность», я знаю, что слышу «копейки - глупые фунты». Вы должны иметь представление о вовлеченных величинах. Только если на это уходит более 1% времени, стоит даже подумать об этом. Обычно, если вы думаете об этом, нет рассматривает проблемы в миллион раз большего размера, и именно это делает программное обеспечение намного медленнее, чем могло бы быть.
@MikeDunlavey изрыгнул ерунду, чтобы удовлетворить свое эго. вы пытаетесь походить на какого-то мудрого монаха, но ничего не говорите. вовлеченные величины ... если только более 1% времени вам следует заботиться ... xD абсолютное ведение. если это неэффективно, об этом стоит знать и исправить. мы здесь и размышляем об этом именно по этой причине! нас не беспокоит, сколько мы можем извлечь из этого знания. и когда я сказал, что вам не нужна меньшая производительность, давай, объясни тогда один чертов сценарий. МИСТЕР УАЙЗ!
@wilhelmtell
Компилятор может исключить временное. Дословно из другой ветки:
Компилятору C++ разрешено исключать временные библиотеки на основе стека, даже если это изменяет поведение программы. Ссылка MSDN для VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
Это не актуально. NRVO избегает необходимости копировать t в "C C :: operator ++ (int)" обратно вызывающей стороне, но i ++ по-прежнему копирует старое значение в стек вызывающей стороны. Без NRVO i ++ создает 2 копии, одну для t и одну обратно для вызывающей стороны.
Руководство по стилю Google C++ говорит:
Preincrement and Predecrement
Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.
Definition: When a variable is incremented (++i or i++) or decremented (--i or i--) and the value of the expression is not used, one must decide whether to preincrement (decrement) or postincrement (decrement).
Pros: When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression. If i is an iterator or other non-scalar type, copying i could be expensive. Since the two types of increment behave the same when the value is ignored, why not just always pre-increment?
Cons: The tradition developed, in C, of using post-increment when the expression value is not used, especially in for loops. Some find post-increment easier to read, since the "subject" (i) precedes the "verb" (++), just like in English.
Decision: For simple scalar (non-object) values there is no reason to prefer one form and we allow either. For iterators and other template types, use pre-increment.
«Решение: для простых скалярных (не объектных) значений нет причин отдавать предпочтение одной форме, и мы разрешаем любую из них. Для итераторов и других типов шаблонов используйте предварительное приращение».
Эх, ... а это что-то?
Указанная ссылка в ответе на данный момент не работает
Я хотел бы отметить отличный пост Эндрю Кенига о Code Talk совсем недавно.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
В нашей компании мы также используем соглашение ++ iter для согласованности и производительности там, где это применимо. Но Эндрю поднимает упущенные детали относительно намерений и производительности. Бывают случаи, когда мы хотим использовать iter ++ вместо ++ iter.
Итак, сначала определитесь с вашим намерением, и если pre или post не имеют значения, тогда переходите к pre, так как это будет иметь некоторое преимущество в производительности, избегая создания дополнительного объекта и его выброса.
Обновленная ссылка: drdobbs.com/architecture-and-design/efficiency-versus-intent /…
@Ketan
...raises over-looked detail regarding intent vs performance. There are times when we want to use iter++ instead of ++iter.
Очевидно, что post и pre-increment имеют разную семантику, и я уверен, что все согласны с тем, что при использовании результата вы должны использовать соответствующий оператор. Думаю, вопрос в том, что делать, когда результат отбрасывается (как в циклах for). Ответ на вопрос это (IMHO) заключается в том, что, поскольку соображения производительности в лучшем случае незначительны, вы должны делать то, что более естественно. Для меня ++i более естественен, но мой опыт подсказывает мне, что я в меньшинстве, и использование i++ вызовет меньше металлических накладных расходов для людей наиболее, читающих ваш код.
В конце концов, именно по этой причине язык не называется "++C". [*]
[*] Вставьте обязательное обсуждение того, что ++C является более логичным именем.
@Motti: (шутит) Имя C++ логично, если вы помните, как Бьярн Страуструп. C++ изначально закодировал его как прекомпилятор, генерирующий программу на C. Следовательно, C++ вернул старое значение C. Или это может быть для улучшения того, что C++ с самого начала имеет несколько концептуальных изъянов.
Марк: Просто хотел указать, что операторы ++ являются хорошими кандидатами для встраивания, и если компилятор решит это сделать, избыточная копия будет удалена в большинстве случаев. (например, типы POD, которые обычно бывают итераторами.)
Тем не менее, в большинстве случаев лучше использовать ++ iter. :-)
Предполагаемый вопрос заключался в том, когда результат не используется (это ясно из вопроса для C). Может ли кто-нибудь исправить это, раз уж вопрос - "вики сообщества"?
По поводу преждевременных оптимизаций часто цитируют Кнута. Верно. но Дональд Кнут никогда не стал бы защищать этот ужасный кодекс, который вы видите в наши дни. Вы когда-нибудь видели a = b + c среди целых чисел Java (не int)? Это составляет 3 преобразования упаковки / распаковки. Важно избегать подобных вещей. И напрасно писать i ++ вместо ++ i - та же ошибка. Обновлено: Как хорошо сказал Френель в комментарии, это можно резюмировать как «преждевременная оптимизация - это зло, как и преждевременная пессимизация».
Даже тот факт, что люди больше привыкли к i ++, является неудачным наследием C, вызванным концептуальной ошибкой K&R (если вы последуете аргументу о намерениях, это логический вывод; и защищать K&R, потому что они K&R бессмысленно, они отлично, но они не так хороши как дизайнеры языка; существует бесчисленное количество ошибок в дизайне C, начиная от gets () и заканчивая strcpy (), и заканчивая API strncpy () (он должен был иметь API strlcpy () с первого дня) ).
Кстати, я один из тех, кто недостаточно знаком с C++, чтобы читать ++ меня раздражает. Тем не менее, я использую это, поскольку признаю, что это правильно.
Я вижу, вы работаете над докторской степенью. с интересом к оптимизации компилятора и тому подобному. Это здорово, но не забывайте, что академия - это эхо-камера, и здравый смысл часто остается за дверью, по крайней мере, в C.S. Вас может заинтересовать это: stackoverflow.com/questions/1303899/…
Я никогда не находил ++i более раздражающим, чем i++ (на самом деле, я нашел его круче), но остальная часть вашего сообщения заслуживает моего полного признания. Может быть, добавить пункт «преждевременная оптимизация - зло, как и преждевременная пессимизация»
strncpy служил определенной цели в файловых системах, которые они использовали в то время; имя файла было 8-символьным буфером, и оно не должно было заканчиваться нулем. Вы не можете винить их в том, что они не видели 40-летнего будущего развития языка.
@MattMcNabb: разве 8-значное имя файла не являлось эксклюзивным для MS-DOS? C был изобретен с помощью Unix. В любом случае, даже если strncpy имел смысл, отсутствие strlcpy не было полностью оправдано: даже в оригинальном C были массивы, которые вы не должны переполнять, а для этого требовалась strlcpy; в лучшем случае это были просто отсутствующие злоумышленники, намеревающиеся воспользоваться ошибками. Но нельзя сказать, что прогнозирование этой проблемы было тривиальным, поэтому, если бы я переписал свой пост, я бы не использовал тот же тон.
@Blaisorblade: Насколько я помню, ранние имена файлов UNIX были ограничены 14 символами. Отсутствие strlcpy() было оправдано тем, что его еще не изобрели.
@Mark: я удалил свой предыдущий ответ, потому что он был немного перевернут и заслужил отрицательное мнение только за это. Я на самом деле думаю, что это хороший вопрос в том смысле, что он спрашивает, что у многих людей на уме.
Обычный ответ: ++ i быстрее, чем i ++, и, без сомнения, так оно и есть, но более серьезный вопрос - «когда вам это нужно?»
Если доля процессорного времени, затрачиваемая на увеличение итераторов, составляет менее 10%, вам может быть все равно.
Если доля времени ЦП, затрачиваемая на увеличение итераторов, превышает 10%, вы можете посмотреть, какие операторы выполняют эту итерацию. Посмотрите, можно ли просто увеличивать целые числа, а не использовать итераторы. Скорее всего, вы могли бы, и хотя это может быть в некотором смысле менее желательным, скорее всего, вы сэкономите практически все время, потраченное на эти итераторы.
Я видел пример, в котором увеличение итератора занимало более 90% времени. В этом случае переход к целочисленному приращению сокращает время выполнения по существу на эту величину. (т.е. лучше, чем 10-кратное ускорение)
Оба такие же быстрые;) Если вы хотите, чтобы это был такой же расчет для процессора, отличается только порядок, в котором он выполняется.
Например, такой код:
#include <stdio.h>
int main()
{
int a = 0;
a++;
int b = 0;
++b;
return 0;
}
Производим следующую сборку:
0x0000000100000f24 <main+0>: push %rbp 0x0000000100000f25 <main+1>: mov %rsp,%rbp 0x0000000100000f28 <main+4>: movl $0x0,-0x4(%rbp) 0x0000000100000f2f <main+11>: incl -0x4(%rbp) 0x0000000100000f32 <main+14>: movl $0x0,-0x8(%rbp) 0x0000000100000f39 <main+21>: incl -0x8(%rbp) 0x0000000100000f3c <main+24>: mov $0x0,%eax 0x0000000100000f41 <main+29>: leaveq 0x0000000100000f42 <main+30>: retq
Вы видите, что для a ++ и b ++ это мнемоника incl, поэтому это одна и та же операция;)
Это C, в то время как OP спросил C++. В C то же самое. В C++ быстрее ++ i; из-за своего объекта. Однако некоторые компиляторы могут оптимизировать оператор постинкремента.
Вот эталон для случая, когда операторы приращения находятся в разных единицах перевода. Компилятор с g ++ 4.5.
Пока не обращайте внимания на проблемы со стилем
// a.cc
#include <ctime>
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
int main () {
Something s;
for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
std::clock_t a = clock();
for (int i=0; i<1024*1024*30; ++i) ++s;
a = clock() - a;
for (int i=0; i<1024*1024*30; ++i) s++; // warm up
std::clock_t b = clock();
for (int i=0; i<1024*1024*30; ++i) s++;
b = clock() - b;
std::cout << "a = " << (a/double(CLOCKS_PER_SEC))
<< ", b = " << (b/double(CLOCKS_PER_SEC)) << '\n';
return 0;
}
// b.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
for (auto it=data.begin(), end=data.end(); it!=end; ++it)
++*it;
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
Результаты (время в секундах) с g ++ 4.5 на виртуальной машине:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 1.70 2.39
-DPACKET_SIZE=50 -O3 0.59 1.00
-DPACKET_SIZE=500 -O1 10.51 13.28
-DPACKET_SIZE=500 -O3 4.28 6.82
Давайте теперь возьмем следующий файл:
// c.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
Он ничего не делает в увеличении. Это моделирует случай, когда приращение имеет постоянную сложность.
Результаты сейчас сильно разнятся:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 0.05 0.74
-DPACKET_SIZE=50 -O3 0.08 0.97
-DPACKET_SIZE=500 -O1 0.05 2.79
-DPACKET_SIZE=500 -O3 0.08 2.18
-DPACKET_SIZE=5000 -O3 0.07 21.90
Если вам не нужно предыдущее значение, возьмите за привычку использовать предварительное приращение. Будьте последовательны даже со встроенными типами, вы привыкнете к этому и не столкнетесь с риском ненужной потери производительности, если вы когда-нибудь замените встроенный тип пользовательским типом.
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. Опять же, вы привыкнете к этому, даже если вы не сейчас.Преждевременная оптимизация - корень всех зол. Как и преждевременная пессимизация.
Интересный тест. Теперь, почти два с половиной года спустя, gcc 4.9 и Clang 3.4 демонстрируют аналогичную тенденцию. Clang работает немного быстрее с обоими, но разница между префиксом и постфиксом хуже, чем в gcc.
Мне бы очень хотелось увидеть реальный пример, где ++ i / i ++ имеет значение. Например, имеет ли это значение для любого из итераторов std?
@JakobSchouJensen: Они предназначались для реальных примеров. Рассмотрим большое приложение со сложными древовидными структурами (например, kd-деревья, квад-деревья) или большими контейнерами, используемыми в шаблонах выражений (чтобы максимизировать пропускную способность данных на оборудовании SIMD). Если это имеет значение, я не совсем уверен, почему можно было бы вернуться к постинкременту для конкретных случаев, если это не требуется с семантической точки зрения.
@phresnel: Я не думаю, что оператор ++ в вашей повседневной жизни является шаблоном выражения - у вас есть реальный пример этого? Обычно оператор ++ используется для целых чисел и итераторов. Это было, я думаю, было бы интересно узнать, есть ли какая-нибудь разница (конечно, нет разницы в целых числах, но итераторах).
@JakobSchouJensen: Нет реального бизнес-примера, но есть приложения для обработки чисел, в которых вы считаете вещи. Что касается итераторов, рассмотрите трассировщик лучей, написанный в идиоматическом стиле C++, и у вас есть итератор для обхода в глубину, такой как 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 и тому подобное. В общем, моя позиция, даже если существует лишь несколько примеров, ...
пост инкремент, если вы имеете в виду, и используйте предварительный инкремент, если вы это имеете в виду (но я уверен, что вы не ставите под сомнение это :))
Разница в производительности между ++i и i++ станет более очевидной, если вы подумаете об операторах как о функциях, возвращающих значение, и о том, как они реализованы. Чтобы упростить понимание того, что происходит, в следующих примерах кода будет использоваться int, как если бы это был struct.
++i увеличивает переменную, тогда возвращает результат. Это можно сделать на месте и с минимальным временем процессора, во многих случаях для этого требуется всего одна строка кода:
int& int::operator++() {
return *this += 1;
}
Но этого нельзя сказать о i++.
Постинкремент, i++, часто рассматривается как возвращающий исходное значение перед приращения. Однако функция может вернуть результат только после завершения. В результате возникает необходимость создать копию переменной, содержащей исходное значение, увеличить переменную, а затем вернуть копию, содержащую исходное значение:
int int::operator++(int& _Val) {
int _Original = _Val;
_Val += 1;
return _Original;
}
Когда нет функциональной разницы между пре-инкрементом и пост-инкрементом, компилятор может выполнить оптимизацию таким образом, чтобы между ними не было разницы в производительности. Однако, если задействован составной тип данных, такой как struct или class, конструктор копирования будет вызываться после инкремента, и будет невозможно выполнить эту оптимизацию, если требуется глубокая копия. Таким образом, предварительное приращение обычно выполняется быстрее и требует меньше памяти, чем последующее приращение.
++i быстрее, чем i++, потому что он не возвращает старую копию значения.
Это также более интуитивно понятно:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
Этот пример C печатает "02" вместо ожидаемого числа "12":
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}
Я не думаю, что ответ (э-э) имеет какое-либо представление о том, чего хочет оператор, или о том, что означает слово «быстрее».
Когда возвращаемое значение не используя, компилятор гарантированно не будет использовать временное значение в случае ++ я. Не гарантируется, что он будет быстрее, но гарантированно не будет медленнее.
Когда с помощью, возвращаемое значение я ++ позволяет процессору подтолкнуть оба инкремент и левая сторона в конвейер, поскольку они не зависят друг от друга. ++ i может остановить конвейер, потому что процессор не может запустить левую часть, пока операция предварительного инкремента не пройдёт весь путь. Опять же, остановка конвейера не гарантируется, так как процессор может найти другие полезные вещи, чтобы застрять.
Пора предоставить людям жемчужины мудрости;) - есть простой трюк, чтобы заставить постфиксное приращение C++ вести себя почти так же, как приращение префикса (придумал это для себя, но видел это также в коде других людей, так что я не один).
По сути, уловка состоит в том, чтобы использовать вспомогательный класс, чтобы отложить инкремент после возврата, и RAII приходит на помощь
#include <iostream>
class Data {
private: class DataIncrementer {
private: Data& _dref;
public: DataIncrementer(Data& d) : _dref(d) {}
public: ~DataIncrementer() {
++_dref;
}
};
private: int _data;
public: Data() : _data{0} {}
public: Data(int d) : _data{d} {}
public: Data(const Data& d) : _data{ d._data } {}
public: Data& operator=(const Data& d) {
_data = d._data;
return *this;
}
public: ~Data() {}
public: Data& operator++() { // prefix
++_data;
return *this;
}
public: Data operator++(int) { // postfix
DataIncrementer t(*this);
return *this;
}
public: operator int() {
return _data;
}
};
int
main() {
Data d(1);
std::cout << d << '\n';
std::cout << ++d << '\n';
std::cout << d++ << '\n';
std::cout << d << '\n';
return 0;
}
Изобретено для тяжелого кода кастомных итераторов, и это сокращает время выполнения. Стоимость префикса по сравнению с постфиксом - это одна ссылка сейчас, и если это пользовательский оператор, который сильно перемещается, префикс и постфикс дали мне одинаковое время выполнения.
++i быстрее, чем i = i +1, потому что в i = i + 1 выполняются две операции: первое приращение и второе присваивание переменной. Но в i++ выполняется только операция приращения.
Я изменил теги, так как эти два тега - самый простой способ найти вопросы такого рода. Я также перебрал другие, у которых не было связных тегов, и дал им связующие теги.