Чи існує a різниця в продуктивності між i ++ і ++ i в C?

Чи існує різниця в продуктивності між i++ і ++i якщо результуюче значення не використовується?

390
24 авг. заданий Mark Harrison 24 Серпня. 2008-08-24 09:48 '08 о 9:48 2008-08-24 9:48
@ 13 відповідей

Резюме: Ні.

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 ++ic 6c6 < for (i = 0; i < 100; i++) --- > for (i = 0; i < 100; ++i) 

Ми скомпілюємо їх, а також отримаємо згенерований асемблер:

 $ gcc -c i++.c ++ic $ gcc -S i++.c ++ic 

І ми бачимо, що і згенерований об'єкт, і файли асемблера збігаються.

 $ md5 i++.s ++is MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e MD5 (++is) = 90f620dda862cd0205cd5db1f2c8c06e $ md5 *.o MD5 (++io) = dd3ef1408d3a9e4287facccec53f7d22 MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22 
332
24 авг. відповідь дан Mark Harrison 24 Серпня. 2008-08-24 09:48 '08 о 9:48 2008-08-24 9:48

Від ефективності і наміри Ендрю Кеніга:

По-перше, далеко не очевидно, що ++i більш ефективний, ніж i++ , по крайней мере, де цілі змінні.

А також:

border=0

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

Отже, якщо результуюче значення не використовується, я б використовував ++i . Але не тому, що він більш ефективний: тому що він правильно формулює мій намір.

91
02 сент. відповідь дан Sébastien RoccaSerra 02 сент. 2008-09-02 14:48 '08 о 14:48 2008-09-02 14:48

Найкращою відповіддю полягає в тому, що ++i буде іноді швидше, але не повільніше.

Все, здається, припускають, що i є звичайним вбудованим типом, таким як int . У цьому випадку не буде помітної різниці.

Однак, якщо i є складним типом, ви можете знайти вимірну різницю. Для i++ ви повинні зробити копію свого класу, перш ніж збільшувати його. Залежно від того, що пов'язано з копією, воно дійсно може бути повільніше, оскільки за допомогою ++it ви можете просто повернути остаточне значення.

 Foo Foo::operator++() { Foo oldFoo = *this; // copy existing value - could be slow // yadda yadda, do increment return oldFoo; } 

Інша відмінність полягає в тому, що з ++i вас є можливість повернути посилання замість значення. Знову ж таки, в залежності від того, що бере участь в створенні копії вашого об'єкта, це може бути повільніше.

Реальним прикладом того, де це може статися, буде використання ітераторів. Копіювання ітератора навряд чи буде пляшкової горловиною в вашому додатку, але все ж гарним вибором звичка використовувати ++i замість i++ де результат не i++ .

42
25 авг. відповідь дан Andrew Grant 25 Серпня. 2008-08-25 07:49 '08 о 7:49 2008-08-25 7:49

Тут додаткове спостереження, якщо ви турбуєтеся про мікро оптимізації. Скорочення циклів може бути "можливо" більш ефективним, ніж приріст циклів (в залежності від архітектури набору команд, наприклад ARM), враховуючи:

 for (i = 0; i < 100; i++) 

На кожному циклі ви будете мати по одній інструкції для:

  1. Додавання 1 в i .
  2. Порівняйте чи i менше 100 .
  3. Умовна гілку, якщо i менше 100 .

У той час як декрементируется цикл:

 for (i = 100; i != 0; i--) 

Цикл буде мати інструкцію для кожного з:

  1. Зменшити i , встановивши прапор стану ЦП.
  2. Умовна гілку залежить від стану регістра процесора ( Z==0 ).

Звичайно, це працює тільки при зменшенні до нуля!

Пам'ятайте про керівництво розробника системи ARM.

16
02 сент. відповідь дан tonylo 02 сент. 2008-09-02 14:39 '08 о 14:39 2008-09-02 14:39

Отримання листа від Скотта Мейерса, більш ефективне c ++ Пункт 6: Розрізняти префіксние і постфіксні форми операцій збільшення і декремента.

Префіксная версія завжди краще постфікса щодо об'єктів, особливо щодо ітераторів.

Причина цього, якщо ви подивитеся на шаблон виклику операторів.

 // Prefix Integer Integer::operator++() { *this += 1; return *this; } // Postfix const Integer Integer::operator++(int) { Integer oldValue = *this; ++(*this); return oldValue; } 

Розглядаючи цей приклад, легко побачити, як оператор префікса завжди буде більш ефективним, ніж постфікси. Через необхідність використання тимчасового об'єкта при використанні постфікса.

Ось чому, коли ви бачите приклади з використанням ітераторів, вони завжди використовують префіксних версію.

Але оскільки ви вказуєте на int, немає ніякої різниці з-за оптимізації компілятора, яка може мати місце.

14
25 авг. відповідь дан JProgrammer 25 Серпня. 2008-08-25 07:29 '08 в 7:29 2008-08-25 7:29

Коротка відповідь:

Немає ніякої різниці між 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.
  • Зберігати адреса масиву в регістрі B.
  • Додайте A і B, збережіть результати в A.
  • На цьому нову адресу, представленому A, збережіть значення x.
  • Зберігати значення i в регістрі A // неефективно, тому що додаткова інструкція тут, ми вже зробили це один раз.
  • Інкрементний регістр A.
  • Зберігати регістр A в i .

компілятор може також підвищити ефективність коду, наприклад:

  • Зберігати значення i в регістрі A.
  • Зберігати адреса масиву в регістрі B.
  • Додайте A і B, збережіть результати в B.
  • Інкрементний регістр A.
  • Зберігати регістр A в i .
  • ... // решта коду.

Просто тому, що ви, як програміст С, навчені думати, що постфікси ++ відбувається в кінці, машинний код не потрібно замовляти таким чином.

Таким чином, немає ніякої різниці між префіксним і Постфіксний ++ в C. Тепер те, що ви, як програміст С, має бути різним, - це люди, які в деяких випадках непослідовно використовують префікс і постфікс в інших випадках, без будь-яких причин. Це говорить про те, що вони не впевнені в тому, як працює C або що у них неправильне знання мови. Це завжди поганий знак, він, в свою чергу, передбачає, що вони роблять інші сумнівні рішення в своїй програмі, засновані на забобонах або "релігійних догмах".

"Префікс ++ завжди швидше" - це дійсно одна з таких помилкових догм, яка поширена серед потенційних програмістів на С.

11
21 окт. відповідь дан Lundin 21 Жовтня. 2014-10-21 12:10 '14 о 12:10 2014-10-21 12:10

Будь ласка, не дозволяйте питання про те, "який з них швидше", є вирішальним фактором для використання. Швидше за все, ви ніколи не будете так піклуватися, і, крім того, час читання програміста набагато дорожче машинного часу.

Використовуйте те, що має сенс для людини, що читає код.

10
20 сент. відповідь дан Andy Lester 20 сент. 2008-09-20 08:09 '08 в 8:09 2008-09-20 8:09

Перш за все: різниця між i++ і ++i непарна в C.


До деталей.

1. Відома проблема C ++: ++i швидше

У C ++, ++i більш ефективний, якщо i - це якийсь об'єкт з перевантаженим оператором збільшення.

Навіщо?
В ++i об'єкт спочатку збільшується, а потім може передаватися як константная посилання на будь-яку іншу функцію. Це неможливо, якщо вираз foo(i++) тому що тепер приріст потрібно виконати до foo() , але старе значення потрібно передати foo() . Отже, компілятор змушений зробити копію i до того, як він виконає оператор інкремента в оригіналі. Додаткові виклики конструктора / деструктора є поганий частиною.

Як зазначено вище, це не відноситься до фундаментальних типам.

2. Маловідомий факт: i++ може бути швидше

Якщо не потрібно викликати конструктор / деструктор, що завжди має місце в C, ++i і i++ повинні бути однаково швидкими, вірно? Ні. Вони практично однаково швидкі, але можуть бути невеликі відмінності, які більшість інших відповідачів помилялися.

Як i++ може бути швидше?
Точка - це залежності даних. Якщо значення потрібно завантажити з пам'яті, необхідно виконати дві наступні операції з ним, збільшити його і використовувати. За допомогою ++i необхідно виконати ++i до того, як значення буде використовуватися. За допомогою i++ використання не залежить від збільшення, а ЦП може виконувати операцію використання паралельно операції інкремента. Різниця становить не більше одного циклу процесора, тому вона дійсно недбала, але вона є. І навпаки, багато хто очікував.

9
04 июня '14 в 21:34 2014-06-04 21:34 відповідь дан cmaster 04 червня '14 о 21:34 2014-06-04 21:34

@Mark Незважаючи на те, що компілятору дозволено оптимізувати тимчасову копію змінної (на основі стека) і gcc (в останніх версіях), це не означає, що всі компілятори завжди будуть робити це.

Я просто тестував його за допомогою компіляторів, які ми використовуємо в нашому поточному проекті, і 3 з 4 НЕ оптимізують його.

Ніколи не припускайте, що компілятор правильно це розуміє, особливо якщо можливо більш швидкий, але не більше повільний код.

Якщо у вас немає дійсно дурною реалізації одного з операторів в вашому коді:

Alwas воліє ++ i в порівнянні з i ++.

7
09 февр. відповідь дан Andreas 09 февр. 2009-02-09 18:40 '09 о 18:40 2009-02-09 18:40

Я можу думати про ситуацію, коли postfix повільніше, ніж приріст приставки:

Уявіть, що процесор з регістром A використовується в якості акумулятора, і він єдиний реєстр, який використовується в багатьох інструкціях (деякі невеликі мікроконтролери насправді схожі на це).

Тепер уявіть наступну програму і їх переклад в гіпотетичну збірку:

Приріст приставки:

 a = ++b + c; ; increment b LD A, [ INC A ST A, [ ; add with c ADD A, [ ; store in a ST A, [> 

Приріст притчі:

 a = b++ + c; ; load b LD A, [ ; add with c ADD A, [ ; store in a ST A, [ ; increment b LD A, [ INC A ST A, [> 

Зверніть увагу, як значення b було примусово перезавантажений. З приростом префікса компілятор може просто збільшити значення і продовжувати використовувати його, можливо, не перезавантажувати його, так як бажане значення вже знаходиться в регістрі після збільшення. Проте, зі збільшенням postfix, компілятор повинен мати справу з двома значеннями: один - старий і один - з доданим значенням, яке, як показано вище, призводить до ще одного доступу до пам'яті.

Звичайно, якщо значення приросту не використовується, наприклад, одне i++; , Компілятор може (і робить) просто згенерувати інструкцію збільшення незалежно від використання постфікса або префікса.


В якості примітки я хотів би згадати, що вираз, в якому є b++ не може бути просто перетворено в одне з ++b без будь-яких додаткових зусиль (наприклад, додавши a- - 1 ). Тому порівняння двох, якщо вони є частиною деякого виразу, дійсно є недійсною. Часто, коли ви використовуєте b++ всередині виразу, ви не можете використовувати ++b , тому, навіть якщо ++b потенційно більш ефективний, це було б просто неправильно. Виняток, звичайно, якщо вираз попросить його (наприклад, a = b++ + 1; який можна змінити на a = ++b; ).

4
04 июня '14 в 20:57 2014-06-04 20:57 відповідь дан Shahbaz 04 червня '14 о 20:57 2014-06-04 20:57

У C компілятор зазвичай може оптимізувати їх, щоб вони були однаковими, якщо результат не використовується.

Однак в C ++ при використанні інших типів, які надають свої власні оператори ++, префиксная версія, ймовірно, буде швидше, ніж версія postfix. Отже, якщо вам не потрібна семантика postfix, краще використовувати префіксний оператор.

4
24 авг. відповідь дан Kristopher Johnson 24 Серпня. 2008-08-24 17:29 '08 о 17:29 2008-08-24 17:29

Я завжди віддаю перевагу pre-increment, однак ...

Я хотів би вказати на те, що навіть в разі виклику функції operator ++ компілятор зможе оптимізувати тимчасове, якщо функція стає вбудованою. Так як operator ++ зазвичай короткий і часто реалізується в заголовку, він, швидше за все, буде вбудований.

Таким чином, для практичних цілей, ймовірно, не дуже важлива різниця між продуктивністю двох форм. Проте, я завжди віддаю перевагу попередній приріст, так як здається, що краще прямо висловити те, що я намагаюся сказати, замість того, щоб покладатися на оптимізатора, щоб зрозуміти це.

Крім того, надання optmizer менш ймовірно, означає, що компілятор працює швидше.

2
16 окт. відповідь дан Andrew Eidsness 16 Жовтня. 2008-10-16 14:28 '08 о 14:28 2008-10-16 14:28

Мій C трохи іржавий, тому я заздалегідь перепрошую. Швидкість, я можу зрозуміти результати. Але я збентежений тим, як обидва файли виходили в один і той же MD5-хеш. Може бути, цикл for працює однаково, але чи не будуть такі два рядки коду генерувати іншу збірку?

 myArray[i++] = "hello"; 

проти

 myArray[++i] = "hello"; 

Перший записує значення в масив, а потім збільшує i. Потім другий приріст я записується в масив. Я не експерт по збірці, але я просто не бачу, як буде генеруватися один і той же виконуваний файл цими двома різними рядками коду.

Тільки мої два цента.

0
24 авг. відповідь дан Jason Z 24 Серпня. 2008-08-24 17:22 '08 о 17:22 2008-08-24 17:22

Інші питання по мітках або Задайте питання