Чи є різниця між продуктивністю i++
та ++i
якщо отримане значення не використовується?
Чи є різниця між продуктивністю 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
++i
замість цього все-таки є хорошою практикою i++
. Немає абсолютно ніяких причин цього не робити, і якщо ваше програмне забезпечення коли-небудь пройде через ланцюжок інструментів, що не оптимізує його, ваше програмне забезпечення буде більш ефективним. Зважаючи на те, що це так само просто, ++i
як і вводити i++
, насправді немає приводу не використовувати ++i
в першу чергу.
Від Ефективності проти наміру Ендрю Кеніга:
По-перше, далеко не очевидно, що
++i
є більш ефективним, ніжi++
, принаймні, щодо цілих змінних.
І:
Тож питання, яке слід задати, не те, яка з цих двох операцій є швидшою, це те, що з цих двох операцій виражає точніше те, що ви намагаєтеся виконати. Я стверджую, що якщо ви не використовуєте значення виразу, ніколи не буде причини використовувати його
i++
замість++i
, тому що ніколи немає причини копіювати значення змінної, збільшувати змінну, а потім відкидати копію.
Отже, якщо отримане значення не буде використано, я б використав ++i
. Але не тому, що вона більш ефективна: тому що вона правильно висловлює мій намір.
i++
так само , як я б код i += n
або i = i + n
, тобто у вигляді мішені дієслова об'єкта , з метою Операнд зліва від дієслова оператора. У випадку з i++
правильним об'єктом немає , але правило все ж діє, зберігаючи ціль зліва від оператора дієслова .
Краща відповідь: ++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++
і ++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
в реєстрі А.i
в регістрі A // неефективно, тому що додаткову інструкцію тут ми вже зробили це один раз.i
.компілятор може також ефективніше створювати код, наприклад:
i
в реєстрі А.i
.Тільки тому, що ви як програміст C навчені думати, що постфікс ++
відбувається в кінці, машинний код не потрібно замовляти таким чином.
Отже, немає різниці між префіксом та постфіксом ++
у 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's різниці фактично немає, оскільки оптимізація компілятора може відбутися.
Ось додаткове спостереження, якщо ви турбуєтесь про мікрооптимізацію. Зменшення циклів може "можливо" бути більш ефективним, ніж нарощування циклів (залежно від архітектури набору інструкцій, наприклад, ARM), якщо:
for (i = 0; i < 100; i++)
У кожному циклі у вас буде одна інструкція для кожного:
1
до i
. i
менше а 100
.i
менша за a 100
.Беручи до уваги цикл зменшення:
for (i = 100; i != 0; i--)
Цикл матиме інструкцію для кожного з:
i
, встановлення прапора статусу реєстру CPU.Z==0
).Звичайно, це працює лише при зменшенні нуля!
Згадано з Посібника для розробників системи ARM.
Будь ласка, не дозволяйте питання "хто швидше" бути вирішальним фактором, який використовувати. Швидше за все, ви ніколи не збираєтеся так сильно піклуватися, до того ж час для читання програміста набагато дорожчий, ніж машинний час.
Використовуйте те, що має найбільше значення для читання коду людиною.
Перш за все: Різниця між i++
і ++i
є незначною в С.
До деталей.
++i
швидшеУ C ++ ++i
більш ефективним iff i
є якийсь об'єкт з перевантаженим оператором приросту.
Чому?
В ++i
об'єкт спочатку збільшується і згодом може передаватися як посилання const на будь-яку іншу функцію. Це неможливо, якщо вираз є foo(i++)
через те, що тепер приріст потрібно зробити раніше, ніж foo()
викликається, але старе значення потрібно передати foo()
. Отже, компілятор змушений зробити копію, i
перш ніж виконувати оператор приросту на оригіналі. Додаткові виклики конструктора / деструктора - це погана частина.
Як зазначалося вище, це не стосується фундаментальних типів.
i++
може бути швидшеЯкщо жодного конструктора / деструктора не потрібно викликати, що завжди є у C, ++i
і він i++
повинен бути однаково швидким, правда? Ні. Вони практично однаково швидкі, але можуть бути невеликі відмінності, через які більшість інших відповідачів отримали неправильний шлях.
Як можна i++
швидше?
Справа в залежності від даних. Якщо значення потрібно завантажити з пам’яті, потрібно виконати дві наступні операції з ним, збільшуючи його та використовуючи. З ++i
, збільшення варто зробити перед тим, як значення можна використовувати. З i++
, використання не залежить від приросту, і ЦП може виконувати операцію використання паралельно операції збільшення. Різниця - це щонайменше один цикл процесора, тож він справді незначний, але він є. І це навпаки, тоді багато хто сподівався б.
++i
або i++
використовується в іншому виразі, зміна між ними змінює семантику виразу, тому будь-яке можливе збільшення / втрата ефективності не викликає сумнівів. Якщо вони є окремими, тобто результат операції використовується не відразу, то будь-який гідний компілятор буде компілювати його до тієї ж речі, наприклад, INC
інструкція по збірці.
i++
і ++i
їх можна використовувати взаємозамінно майже в будь-якій можливій ситуації, регулюючи константи циклу на одну, так що вони майже еквівалентні тому, що вони роблять для програміста. 2) Незважаючи на те, що обидва компілюються в одній інструкції, їх виконання відрізняється для ЦП. У випадку з i++
CPU може обчислити приріст паралельно деякій іншій інструкції, яка використовує те саме значення (процесори дійсно це роблять!), Тоді як з ++i
процесором доводиться планувати іншу інструкцію після збільшення.
if(++foo == 7) bar();
і if(foo++ == 6) bar();
є функціонально рівнозначними. Однак другий може бути на один цикл швидшим, тому що порівняння та приріст можна обчислити паралельно CPU. Не те, що цей єдиний цикл має велике значення, але різниця є.
<
наприклад, vs <=
) там, де ++
зазвичай використовується, тому перетворення між тим не менш часто можливе.
@Mark Незважаючи на те, що компілятору дозволено оптимізувати тимчасову копію змінної (на основі стека), і gcc (в останніх версіях) робить це, не означає, що всі компілятори завжди будуть робити це.
Я просто перевірив це разом із компіляторами, які ми використовуємо в нашому поточному проекті, і 3 з 4 не оптимізують його.
Ніколи не припускайте, що компілятор це правильно відповідає, особливо якщо можливо швидший, але ніколи повільний код не так легко читати.
Якщо у вас немає дійсно нерозумної реалізації одного з операторів у вашому коді:
Завжди віддаю перевагу ++ i над i ++.
У C компілятор може, як правило, оптимізувати їх, щоб вони були однаковими, якщо результат не використовується.
Однак у C ++, якщо використовуються інші типи, які надають власні оператори ++, версія префікса, швидше за все, буде швидшою, ніж версія постфіксу. Отже, якщо вам не потрібна семантика постфіксу, краще скористатися оператором префікса.
Я можу придумати ситуацію, коли постфікс повільніше, ніж приріст префікса:
Уявіть, що процесор з регістром 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
(і, можливо, напрочуд, --i
було більш ефективним, ніж i--
). Це для компіляторів С для DEC PDP-11!
PDP-11 мав інструкцію по збірці для попереднього зменшення регістра та післязростання, але не навпаки. Вказівки дозволяли використовувати будь-який регістр загального призначення як покажчик стека. Отже, якщо ви використовували щось подібне, *(i++)
воно могло б бути зібране в одну інструкцію по збірці, тоді як *(++i)
не могло.
Це, очевидно, дуже езотеричний приклад, але він дає виняток, коли пост-приріст є більш ефективним (або, я повинен сказати, був , оскільки в наші дні не існує великого попиту на код PDP-11 C).
--i
і i++
.
Я завжди віддаю перевагу попередній приріст, однак ...
Я хотів би зазначити, що навіть у випадку виклику функції ++ оператор, компілятор зможе оптимізувати тимчасове, якщо функція стане вбудованою. Оскільки оператор ++ зазвичай короткий і часто реалізований у заголовку, він, ймовірно, стане вкладеним.
Отже, для практичних цілей, різниця між виконанням двох форм, ймовірно, не має великої різниці. Однак я завжди віддаю перевагу попередньому збільшенню, оскільки, здається, краще безпосередньо висловити те, що я намагаюся сказати, а не покладатися на оптимізатор, щоб розібратися в цьому.
Крім того, надання оптимізатору менше робити ймовірно означає, що компілятор працює швидше.
Мій C трохи іржавий, тому заздалегідь прошу вибачення. Швидко, я можу зрозуміти результати. Але я заплутаний у тому, як обидва файли вийшли на один і той же хеш MD5. Можливо, цикл для циклу працює однаково, але чи не будуть наступні 2 рядки коду генерувати різні збірки?
myArray[i++] = "hello";
проти
myArray[++i] = "hello";
Перший записує значення в масив, потім збільшується i. Другий приріст я потім записує в масив. Я не фахівець з монтажу, але просто не бачу, як той самий виконуваний файл буде генерований цими двома різними рядками коду.
Всього два мої копійки.
foo[i++]
до foo[++i]
нічого не змінюючи інше, очевидно , змінити семантику програми, але на деяких процесорах при використанні компілятора без оптимізації логіки петлі вантажопідйомної, збільшуються p
і q
один раз , а потім запустити цикл , який виконує , наприклад , *(p++)=*(q++);
буде швидше , ніж з допомогою циклу , який виконує *(++pp)=*(++q);
. Для дуже вузьких циклів на деяких процесорах різниця швидкостей може бути значною (більше 10%), але це, мабуть, єдиний випадок у С, де пост-приріст істотно швидший, ніж попередній приріст.