Есть ли разница в производительности между i ++ и ++ i в C ++?

У нас есть вопрос, есть ли разница в производительности между i++и ++i в C ?

Какой ответ для C ++?

24.08.2008 07:14:22
Я сделал пометку, поскольку эти два тега - самый простой способ найти вопросы такого рода. Я также прошел через других, у которых не было связанных признаков, и дал им связанные признаки.
George Stocker 10.09.2009 15:25:09
Есть ли разница в производительности между использованием C ++ и ++ C?
new123456 26.06.2011 01:53:40
Статья: Разумно ли использовать префиксный оператор приращения ++ it вместо постфиксного оператора it ++ для итераторов? - viva64.com/ru/b/0093
user965097 16.04.2015 14:35:00
17 ОТВЕТОВ
РЕШЕНИЕ

[Резюме: используйте, ++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переменную и связанный с ней конструктор копирования. Если конструктор копирования стоит дорого, это может оказать существенное влияние на производительность.

422
29.03.2018 20:08:56
Чего может избежать компилятор, так это второй копии, возвращающей tmp, путем выделения tmp в вызывающей стороне через NRVO, как упомянуто в другом комментарии.
Blaisorblade 15.01.2009 00:13:08
Разве компилятор не может этого избежать, если оператор ++ встроен?
Eduard - Gabriel Munteanu 18.02.2009 15:59:13
Да, если оператор ++ встроен и tmp никогда не используется, его можно удалить, если у конструктора или деструктора объекта tmp нет побочных эффектов.
Zan Lynx 9.09.2009 01:21:15
@kriss: разница между C и C ++ заключается в том, что в C у вас есть гарантия, что оператор будет встроен, и в этот момент достойный оптимизатор сможет устранить разницу; вместо этого в C ++ нельзя предполагать встраивание - не всегда.
Blaisorblade 5.03.2012 10:45:58
Я бы +1, если бы ответ упомянул кое-что о классах, которые содержат указатели (авто, умные или примитивные) на динамически распределяемую (кучу) память, где конструктор копирования обязательно выполняет глубокие копии. В таких случаях аргумент отсутствует, ++ i, возможно, на порядок эффективнее i ++. Ключевой момент заключается в том, чтобы привыкнуть использовать преинкремент, когда семантика постинкремента фактически не требуется вашим алгоритмом, и тогда вы будете иметь привычку писать код, который по своей природе поддается большей эффективности, независимо от того, как ну ваш компилятор может оптимизировать.
phonetagger 17.07.2012 18:57:22

Да. Есть.

Оператор ++ может быть или не быть определен как функция. Для примитивных типов (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), вы должны создавать копию, и компилятор ничего не может с этим поделать. Когда предоставляется выбор, используйте оператор ++ (); Таким образом, вы не сохраняете копию. Это может быть значительным в случае многих приращений (большой цикл?) И / или больших объектов.

64
12.07.2013 10:45:13
«Оператор предварительного приращения вводит зависимость данных в коде: ЦП должен дождаться завершения операции приращения, прежде чем его значение может быть использовано в выражении. На глубоко конвейерном ЦП это приводит к остановке. Зависимости данных нет для оператора увеличения поста. " ( Архитектура игрового движка (2-е издание) ) Таким образом, если копия пост-инкремента не требует большого объема вычислений, она все равно может превзойти предварительное приращение.
Matthias 9.10.2017 18:18:44
В постфиксном коде, как это работает? C t(*this); ++(*this); return t;Во второй строке вы увеличиваете указатель this вправо, поэтому как tобновляться, если вы увеличиваете это. Не были ли скопированы значения этого t?
rasen58 11.11.2017 03:37:31
The operator++(int) function must create a copy.нет. Не больше копий, чемoperator++()
Severin Pappadeux 7.04.2019 22:29:57

Не совсем правильно говорить, что компилятор не может оптимизировать удаление временной переменной в случае постфикса. Быстрый тест с 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)] 

Таким образом, хотя постфиксная версия может быть медленнее, вполне возможно, что оптимизатор будет достаточно хорош, чтобы избавиться от временной копии, если вы ее не используете.

20
24.08.2008 14:41:21
Вы забыли отметить важный момент, что здесь все указано. Если определения операторов недоступны, нельзя избежать копирования, выполненного во внешнем коде; с встраиванием оптимизатора вполне очевидно, так что любой компилятор сделает это.
Blaisorblade 15.01.2009 00:01:39

Причина, по которой вы должны использовать ++ i даже на встроенных типах, в которых нет никакого преимущества в производительности, заключается в создании хорошей привычки для себя.

0
25.08.2008 04:52:45
Извините, но это беспокоит меня. Кто говорит, что это «хорошая привычка», когда это почти никогда не имеет значения? Если люди хотят сделать это частью своей дисциплины, это нормально, но давайте отличим существенные причины от вопросов личного вкуса.
Mike Dunlavey 24.08.2009 11:26:52
@MikeDunlavey хорошо, так какую сторону вы обычно используете, когда это не имеет значения? xD это либо один, либо другой, не так ли? post ++ (если вы используете его с общим смыслом. обновите его, верните старый) полностью уступает ++ pre (обновите его, верните), никогда не было причин, по которым вы хотели бы иметь меньшую производительность. в случае, если вы захотите обновить его после, программист вообще не будет делать post ++. не тратя время на копирование, когда оно у нас уже есть. обновите его после того, как мы его используем. затем компиляторы, имеющие здравый смысл, который вы хотели бы иметь.
Puddle 21.12.2018 15:04:44
@Puddle: Когда я слышу это: «нет никаких причин, по которым вы хотели бы иметь меньшую производительность», я знаю, что слышу «глупый пенни». Вы должны иметь представление о масштабах. Только если это составляет более 1% времени, вы должны подумать об этом. Обычно, если вы думаете об этом, есть проблемы в миллион раз больше, которые вы не рассматриваете, и это делает программное обеспечение намного медленнее, чем могло бы быть.
Mike Dunlavey 24.12.2018 02:28:50
@MikeDunlavey отрыгнул ерунду, чтобы удовлетворить ваше эго. ты пытаешься звучать как какой-то мудрый монах, но ничего не говоришь. вовлеченные величины ... если только в течение 1% времени вы должны заботиться ... xD абсолютный дриблинг. если это неэффективно, это стоит знать и исправить. мы здесь обдумываем это именно по этой причине! нас не волнует, сколько мы можем получить от этого знания. и когда я сказал, что вы не хотите снижать производительность, тогда объясните один чертов сценарий. Мистер Мудрый!
Puddle 24.12.2018 15:33:49

@wilhelmtell

Компилятор может исключить временный. Дословно из другой ветки:

Компилятору C ++ разрешено устранять временные эффекты на основе стека, даже если это изменяет поведение программы. Ссылка MSDN для VC 8:

http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx

1
3.07.2012 14:56:32
Это не актуально. NRVO избавляет от необходимости копировать t в "CC :: operator ++ (int)" обратно в вызывающую сторону, но i ++ все равно будет копировать старое значение в стеке вызывающей стороны. Без NRVO i ++ создает 2 копии, одну для t и одну обратно для вызывающей стороны.
Blaisorblade 15.01.2009 00:09:26

В Google C ++ Style Guide говорит:

Прединкремент и Предкремент

Используйте префиксную форму (++ i) операторов увеличения и уменьшения с итераторами и другими объектами шаблона.

Определение: когда переменная увеличивается (++ i или i ++) или уменьшается (--i или i--) и значение выражения не используется, необходимо решить, будет ли преинкремент (декремент) или постинкремент (декремент).

Плюсы: Когда возвращаемое значение игнорируется, форма "pre" (++ i) никогда не менее эффективна, чем форма "post" (i ++), и часто более эффективна. Это потому, что постинкремент (или декремент) требует создания копии i, которая является значением выражения. Если я итератор или другой нескалярный тип, копирование может быть дорогим. Поскольку два типа приращения ведут себя одинаково, когда значение игнорируется, почему бы просто не всегда выполнить предварительное приращение?

Минусы: в Си сложилась традиция использовать постинкремент, когда значение выражения не используется, особенно в циклах for. Некоторые считают, что постинкремент легче читать, поскольку «subject» (i) предшествует «глаголу» (++), как и в английском.

Решение: для простых скалярных (необъектных) значений нет причин предпочитать одну форму, и мы разрешаем любую. Для итераторов и других типов шаблонов используйте предварительное увеличение.

14
1.05.2015 11:11:50
«Решение: для простых скалярных (необъектных) значений нет причин предпочитать одну форму, и мы допускаем любую. Для итераторов и других типов шаблонов используйте предварительное увеличение».
Nosredna 28.11.2009 17:41:47
Эх, ... а что это такое?
Sebastian Mach 15.11.2014 10:06:33
Упомянутая ссылка в ответе в настоящее время не работает
karol 12.07.2019 07:12:06

Я хотел бы отметить отличный пост Эндрю Кенига о Code Talk совсем недавно.

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

В нашей компании мы также используем соглашение ++ iter для согласованности и производительности, где это применимо. Но Эндрю поднимает упущенные детали относительно намерения против производительности. Иногда мы хотим использовать iter ++ вместо ++ iter.

Итак, сначала определитесь с вашим намерением, и если pre или post не имеют значения, тогда переходите к pre, поскольку это принесет некоторую выгоду производительности, избегая создания дополнительного объекта и выбрасывая его.

4
3.09.2008 04:02:31
Ofek Shilon 4.05.2017 05:47:41

@Ketan

... поднимает упущенные детали относительно намерения против производительности. Иногда мы хотим использовать iter ++ вместо ++ iter.

Очевидно, что post и pre-increment имеют разную семантику, и я уверен, что все согласны с тем, что при использовании результата вы должны использовать соответствующий оператор. Я думаю, что вопрос в том, что нужно делать, когда результат отбрасывается (как в forциклах). Ответ на этот вопрос (ИМХО) заключается в том, что, поскольку соображения производительности в лучшем случае незначительны, вы должны делать то, что более естественно. Для меня ++iэто более естественно, но мой опыт подсказывает мне, что я в меньшинстве, и использование i++приведет к меньшим затратам металла для большинства людей, читающих ваш код.

Ведь именно поэтому язык не называется " ++C". [*]

[*] Включить обязательное обсуждение ++Cболее логичного имени.

4
3.12.2008 12:05:59
@Motti: (шутит) Имя C ++ логично, если вспомнить Bjarne Stroustrup C ++, который изначально кодировал его как прекомпилятор, генерирующий программу на C. Следовательно, C ++ вернул старое значение C. Или может быть, что C ++ с самого начала несколько концептуально несовершенен.
kriss 31.03.2010 11:37:04

Марк: Просто хотел бы отметить, что операторы ++ являются хорошими кандидатами для встраивания, и если компилятор решит это сделать, избыточная копия будет исключена в большинстве случаев. (например, типы POD, которые обычно являются итераторами.)

Тем не менее, в большинстве случаев все еще лучше использовать ++ iter. :-)

3
16.09.2008 11:21:27

Намеченный вопрос был о том, когда результат не используется (это ясно из вопроса для 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 ++, чтобы найти ++, которую я раздражаю читать. Тем не менее, я использую это, так как я признаю, что это правильно.

0
5.03.2012 10:34:46
Я вижу, вы работаете над докторской диссертацией. с интересом к оптимизации компилятора и тому подобного. Это здорово, но не забывайте, что академия - это эхо-камера, и здравый смысл часто остается за дверью, по крайней мере, в CS. Вам может быть интересно это: stackoverflow.com/questions/1303899/…
Mike Dunlavey 23.08.2009 18:28:44
Я никогда не находил ++iбольше раздражающего, чем i++(на самом деле, я нашел это круче), но остальная часть вашего поста получает мое полное признание. Возможно, добавьте пункт «преждевременная оптимизация - это зло, равно как и преждевременная пессимизация»
Sebastian Mach 1.03.2012 15:50:24
strncpyслужил цели в файловых системах, которые они использовали в то время; имя файла было 8-символьным буфером, и оно не должно заканчиваться нулем. Вы не можете обвинить их в том, что они не видели 40 лет в будущем языковой эволюции.
M.M 2.09.2014 22:15:17
@MattMcNabb: не было ли 8-символьное имя файла эксклюзивным для MS-DOS? Си был изобретен с Unix. В любом случае, даже если у strncpy была точка, отсутствие strlcpy не было полностью оправдано: даже у оригинального C были массивы, которые вы не должны переполнять, что требовало strlcpy; самое большее, им не хватало только злоумышленников, намеревающихся использовать ошибки. Но нельзя сказать, что прогнозирование этой проблемы было тривиальным, поэтому, если бы я переписал свой пост, я бы не использовал тот же тон.
Blaisorblade 3.09.2014 17:37:55
@Blaisorblade: Насколько я помню, ранние имена файлов UNIX были ограничены 14 символами. Отсутствие strlcpy()было оправдано тем, что оно еще не было изобретено.
Keith Thompson 20.04.2015 19:12:39

@Mark: я удалил свой предыдущий ответ, потому что это было немного перевернуто, и заслужил понижение для этого одного. Я на самом деле думаю, что это хороший вопрос в том смысле, что он спрашивает, что думают многие люди.

Обычный ответ таков: ++ я быстрее, чем i ++, и, без сомнения, так и есть, но главный вопрос - когда тебя это волнует?

Если доля процессорного времени, затрачиваемого на инкрементные итераторы, составляет менее 10%, вам может быть все равно.

Если доля процессорного времени, затрачиваемого на увеличение итераторов, превышает 10%, вы можете посмотреть, какие операторы выполняют эту итерацию. Посмотрите, можете ли вы просто увеличивать целые числа, а не использовать итераторы. Скорее всего, вы могли бы, и, хотя это может быть в некотором смысле менее желательно, шансы довольно хороши, вы сэкономите практически все время, проведенное в этих итераторах.

Я видел пример, когда увеличение итератора занимало более 90% времени. В этом случае переход к целочисленному приращению сокращает время выполнения по существу на эту величину. (т.е. лучше, чем 10-кратное ускорение)

1
23.08.2009 19:04:56

И то, и другое так же быстро;) Если вы хотите, чтобы для процессора использовались одинаковые вычисления, различается только порядок их выполнения.

Например, следующий код:

#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 ++ это мнемоника вкл, так что это та же самая операция;)

0
11.04.2011 08:27:32
Это C, а OP спросил C ++. В Си тоже самое. В C ++ быстрее работает ++ i; из-за его объекта. Однако некоторые компиляторы могут оптимизировать оператор постинкремента.
Wiggler Jtag 25.02.2015 21:13:49

Вот пример для случая, когда операторы приращения находятся в разных единицах перевода. Компилятор с 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;
}

O (n) приращение

Контрольная работа

// 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

O (1) приращение

Контрольная работа

Давайте теперь возьмем следующий файл:

// 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. Опять же, вы привыкнете к этому, даже если вы не сейчас.

Кнут.

Преждевременная оптимизация - корень всего зла. Как и преждевременная пессимизация.

47
1.03.2012 15:51:25
Интересный тест. Теперь, почти два с половиной года спустя, gcc 4.9 и Clang 3.4 показывают аналогичную тенденцию. Clang немного быстрее с обоими, но несоответствие между pre и postfix хуже, чем gcc.
chew socks 14.08.2014 15:06:07
То, что я действительно хотел бы видеть, является реальным примером, где ++ i / i ++ имеет значение. Например, имеет ли это значение на любом из итераторов std?
Jakob Schou Jensen 4.06.2015 06:58:45
@JakobSchouJensen: Это были довольно реальные примеры. Рассмотрим большое приложение со сложными древовидными структурами (например, kd-деревьями, quad-деревьями) или большими контейнерами, используемыми в шаблонах выражений (чтобы максимизировать пропускную способность данных на оборудовании SIMD). Если это что-то меняет, я не совсем уверен, почему можно было бы перейти к постинкременту для конкретных случаев, если это не требуется семантически.
Sebastian Mach 5.06.2015 08:31:49
@phresnel: Я не думаю, что operator ++ в вашем повседневном использовании является шаблоном выражения - у вас есть реальный пример этого? Типичное использование оператора ++ для целых чисел и итераторов. Это было бы, я думаю, было бы интересно узнать, есть ли какая-либо разница (конечно, нет разницы в целых числах - кроме итераторов).
Jakob Schou Jensen 6.06.2015 15:35:38
@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и тому подобное. В общем, моя позиция такова, даже если существует только несколько примеров ...
Sebastian Mach 7.06.2015 20:16:07

Разница в производительности между ++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, конструктор копирования будет вызываться после инкремента, и эта оптимизация будет невозможна, если требуется глубокое копирование. Таким образом, предварительное увеличение обычно происходит быстрее и требует меньше памяти, чем последующее увеличение.

3
6.11.2012 22:24:20

++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;
}

То же самое для C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
-5
1.05.2015 11:03:11
  1. ++ я - быстрее, не используя возвращаемое значение
  2. i ++ - быстрее, используя возвращаемое значение

Когда не используется возвращаемое значение, компилятор гарантированно не использует временный в случае ++ i . Не гарантируется, что будет быстрее, но гарантированно не будет медленнее.

При использовании возвращаемого значения i ++ позволяет процессору вставлять в конвейер как инкремент, так и левую сторону, поскольку они не зависят друг от друга. ++ Я могу остановить конвейер, потому что процессор не может запустить левую сторону до тех пор, пока операция предварительного приращения не будет извилистой до конца. Опять же, остановка конвейера не гарантируется, так как процессор может найти другие полезные вещи, чтобы застрять.

4
5.05.2017 21:13:13

Пришло время предоставить людям драгоценные камни мудрости;) - есть простой трюк, чтобы заставить приращение постфикса 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;
}

Придумано для некоторых тяжелых пользовательских кодов итераторов, и это сокращает время выполнения. Стоимость префикса по сравнению с постфиксом теперь является одной ссылкой, и если это пользовательский оператор, выполняющий тяжелые перемещения, префикс и постфикс дают мне одинаковое время выполнения.

0
29.03.2018 20:48:43