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

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

Связанный, тот же вопрос, но явно для С ++ - stackoverflow.com/questions/24901/…

FluxLemur 11.08.2019 02:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
493
1
113 397
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

Резюме: Нет.

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

Я знаю, что этот вопрос касается C, но мне было бы интересно узнать, могут ли браузеры выполнять эту оптимизацию для javascript.

TM. 13.08.2009 01:58

Даже если бы компилятор не оптимизировал, вы вряд ли заметите разницу на любом современном процессоре.

wds 22.01.2010 10:54

Итак, «Нет» верно для одного компилятора, с которым вы тестировали.

Andreas 25.01.2011 11:59

@Andreas: Хороший вопрос. Раньше я писал компилятор и имел возможность протестировать этот код на многих процессорах, операционных системах и компиляторах. Единственный компилятор, который я обнаружил, который не оптимизировал случай i ++ (фактически, компилятор, который привлек мое профессиональное внимание к этому), был компилятором Software Toolworks C80 Уолта Билофски. Этот компилятор был для систем Intel 8080 CP / M. Можно с уверенностью сказать, что любой компилятор, который не включает эту оптимизацию, не предназначен для общего использования.

Mark Harrison 25.01.2011 23:55

Кроме того, no проверяется только на целые числа, однако в вопросе не упоминается тип i. Тогда ваше обобщение в строке 1. Простите, -1.

Sebastian Mach 15.12.2011 17:04

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

monokrome 05.07.2012 21:45

@Andreas: Большинство компиляторов оптимизируют это. Компилятору не имеет смысла использовать i ++, если вы нигде не используете его возвращаемое значение. Однако та же самая логика должна распространяться вплоть до программиста. Полагаться на оптимизацию - проблема сама по себе.

monokrome 05.07.2012 21:48

@monokrome Поскольку разработчики могут предоставить свою собственную реализацию для операторов префикса и постфикса на многих языках, это не всегда может быть приемлемым решением для компилятора без предварительного сравнения этих функций, что может быть нетривиальным.

pickypg 20.09.2012 04:50

@pickypg, хорошее замечание о различиях в языках. Совет monokrome звучит для C, и именно к этому относится этот конкретный вопрос.

Mark Harrison 20.09.2012 05:07

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

pickypg 20.09.2012 05:09

@pickypg Обратите внимание на вопросы здесь. Этот вопрос касается языка C, поэтому ваша точка зрения неверна в контексте обсуждения здесь. В C замечательно то, что он не поддерживает перегрузку операторов.

monokrome 24.01.2013 01:39

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

noop 09.08.2013 02:04

В ubuntu 14.04 64bits контрольные суммы файлов * .o (или файлов * .s) отличаются, даже если мы принудительно оптимизируем с параметром -O3. Конечно, это не доказывает, что одни и те же коды сборки разные.

Olorin 26.09.2014 00:25

Это все еще называется оптимизацией. По какой-то причине это делается даже с нулевыми флагами оптимизации, но это определенно не то же самое, что реализация оператора, который ничего не делает (например, i;), поскольку базовый код «должен» по крайней мере копировать регистр при использовании постфиксного оператора.

maxbc 11.02.2016 14:16

@monokrome очень хорошие моменты, и я с ними согласен. Особенно о том, как эта логика должна распространяться на программиста. Еще одна причина, по которой ++i должен использоваться по умолчанию, заключается в том, что легче увидеть, что что-то увеличивается. Возьмем, к примеру, reallylongvariablename++; против ++reallylongvariablename;. Намного легче увидеть, что первый увеличивается (например, когда он находится в отдельной строке). И это больше соответствует тому, как вы читаете это в уме, «приращение i» -> «++ i».

RastaJedi 18.04.2016 13:45

Я повторил описанный выше сценарий с циклом, используя size_t, и мои хеши другие. Запуск g ++ 5.3.1 в Ubuntu 16.04.

Steven Walton 14.05.2016 01:19

@StevenWalton сравните файлы .s и посмотрите, чем они отличаются

Mark Harrison 14.05.2016 01:20

diff i++.s ++i.s показывает только имя файла и 1c1 вверху. Различие файлов cpp просто показывает разницу в i ++ и ++ i. Я провел небольшой тест и обнаружил, что ++ i работает немного быстрее. Но общее время работы составило около 3 секунд. После 10 прогонов каждый ++ i был средним 3,3153, а i ++ был средним 3,3248. Увидел такую ​​же относительную разницу при перемещении петель на ~ 30 секунд. Совсем не большая разница.

Steven Walton 14.05.2016 01:47

Мой C немного заржавел, поэтому заранее прошу прощения. Поспешно, я могу понять результаты. Но меня смущает то, как оба файла попали в один и тот же хеш MD5. Может быть, цикл for работает одинаково, но разве следующие 2 строки кода не генерируют другую сборку?

myArray[i++] = "hello";

против

myArray[++i] = "hello";

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

Всего два цента.

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

John Gardner 20.09.2008 09:15

@Jason Z Оптимизация компилятора происходит до того, как сборка будет завершена, он увидит, что переменная i не используется где-либо еще в той же строке, поэтому сохранение ее значения было бы пустой тратой, она, вероятно, эффективно переворачивает ее на i ++. Но это только предположение. Я не могу дождаться, пока один из моих лекторов попытается сказать, что это быстрее, и я стану тем парнем, который исправляет теорию практическими доказательствами. Я уже почти чувствую враждебность ^ _ ^

Tarks 24.08.2008 18:35

Исправление: «компилятор увидит, что возвращаемое значение i ++ не используется, поэтому он перевернет его на ++ i». То, что вы написали, неверно еще и потому, что у вас не может быть i вместе с одним из i ++, i ++ в одной строке (операторе), этот результат не определен.

Blaisorblade 15.01.2009 03:18

«Мой C немного заржавел, поэтому заранее прошу прощения». В вашем C все в порядке, но вы не прочитали полностью исходный вопрос: «Есть ли разница в производительности между i ++ и ++ i если полученное значение не используется?». Обратите внимание на курсив. В вашем примере используется «результат» i ++ / ++ i является, но в идиоматическом цикле for «результат» оператора pre / postincrement не используется, поэтому компилятор может делать то, что ему нравится.

Roddy 01.09.2008 20:22

Изменение foo[i++] на foo[++i] без изменения чего-либо еще, очевидно, изменило бы семантику программы, но на некоторых процессорах при использовании компилятора без логики оптимизации с подъемом цикла, увеличивая p и q один раз, а затем запуская цикл, который выполняется, например, *(p++)=*(q++); будет быстрее, чем использование цикла, который выполняет *(++pp)=*(++q);. Для очень плотных циклов на некоторых процессорах разница в скорости может быть значительной (более 10%), но это, вероятно, единственный случай в C, где постинкремент существенно быстрее, чем прединкремент.

supercat 08.09.2014 22:34

В 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, фактически нет никакой разницы из-за возможной оптимизации компилятора.

Я думаю, что его вопрос был направлен на C, но в отношении C++ вы абсолютно правы, и, кроме того, люди C должны принять это, поскольку они могут использовать его и для C++. Я слишком часто вижу, как программисты на C используют постфиксный синтаксис ;-)

Anders Rune Jensen 16.12.2008 18:39

-1 Хотя это хороший совет для программистов на C++, он ни в малейшей степени не отвечает на вопрос, помеченный тегом C. В C абсолютно безразлично, используете ли вы префикс или постфикс.

Lundin 21.10.2014 10:41

Префикс @Lundin и postifx имеют значение в C согласно ответу Андреас. Вы не можете предположить, что компилятор будет оптимизировать, и это просто хорошая практика для любого языка для Всегда предпочитаю ++ i над i ++

JProgrammer 24.10.2014 07:02

@JProgrammer Они не имеют значения, согласно вашему искреннему ответу :) Если вы обнаружите, что они дают другой код, это либо потому, что вам не удалось включить оптимизацию, либо потому, что компилятор плохой. В любом случае, ваш ответ не по теме, так как вопрос был о C.

Lundin 24.10.2014 10:17

Лучше ответить, что ++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++ там, где это не влияет на результат.

В вопросе явно указано C, без ссылки на C++.

Dan Cristoloveanu 11.10.2008 03:00

Этот (по общему признанию, старый) вопрос касался C, а не C++, но я полагаю, что также стоит упомянуть, что в C++, даже если класс реализует операторы после и до исправлений, они даже не обязательно связаны. Например, bar ++ может увеличивать один член данных, в то время как ++ bar может увеличивать другой член данных, и в этом случае у вас не будет возможности использовать ни один из них, поскольку семантика различна.

Kevin 10.03.2014 23:51

-1 Хотя это хороший совет для программистов на C++, он ни в малейшей степени не отвечает на вопрос, помеченный тегом C. В C абсолютно безразлично, используете ли вы префикс или постфикс.

Lundin 21.10.2014 10:42

@Pacerier Вопрос помечен тегами C и только C. Как вы думаете, почему они в этом не заинтересованы? Учитывая, как работает SO, не лучше ли было бы предположить, что они Только заинтересованы в C, а не в Java, C#, COBOL или любом другом языке, не относящемся к теме?

Lundin 03.06.2015 09:51

Вот еще одно наблюдение, если вас беспокоит микрооптимизация. Уменьшение циклов «возможно» может быть более эффективным, чем увеличение циклов (в зависимости от архитектуры набора команд, например, ARM), при условии:

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

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

  1. Добавление 1 в i.
  2. Сравните, меньше ли i, чем 100.
  3. Условный переход, если i меньше 100.

В то время как убывающий цикл:

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

В цикле будет инструкция для каждого из:

  1. Уменьшите i, установив флаг состояния регистра CPU.
  2. Условный переход в зависимости от состояния регистра ЦП (Z==0).

Конечно, это работает только при уменьшении до нуля!

Вспомнил из Руководства разработчика системы ARM.

Хороший. Но разве при этом не будет меньше попаданий в кеш?

AndreasT 11.08.2009 18:17

Есть старый странный трюк из книг по оптимизации кода, сочетающий преимущество ветки с нулевым тестом и увеличивающийся адрес. Вот пример на языке высокого уровня (вероятно, бесполезный, поскольку многие компиляторы достаточно умны, чтобы заменить его эффективным, но более распространенным кодом цикла меньше): int a [N]; для (i = -N; i; ++ i) a [N + i] + = 123;

noop 24.07.2010 03:38

@noop Не могли бы вы уточнить, на какие книги по оптимизации кода вы ссылаетесь? Я изо всех сил пытался найти хорошие.

mezamorphic 14.05.2012 16:45

Этот ответ вовсе не является ответом на вопрос.

monokrome 05.07.2012 21:54

@mezamorphic Для x86 моими источниками являются: руководства по оптимизации Intel, Agner Fog, Paul Hsieh, Michael Abrash.

noop 02.10.2012 13:54

@noop: не потеряет ли прирост эффективности в итерации цикла из-за дополнительной работы, требуемой a[N+i] вместо a[i].

Lawrence Dol 18.01.2013 23:14

@SoftwareMonkey и [N + i], и [i] генерируют одну инструкцию для архитектуры Intel, если N является постоянным. Кроме того, если N не является постоянным, вы просто заменяете [N + i] на a_end [i], где int * a_end = a + N. И поскольку значение a + N не изменяется внутри тела цикла, оно возможно, что оптимизатор выполнит эту замену за вас.

noop 09.08.2013 01:28

Из материала Эффективность против намерения Эндрю Кенига:

First, it is far from obvious that ++i is more efficient than i++, at least where integer variables are concerned.

И :

So the question one should be asking is not which of these two operations is faster, it is which of these two operations expresses more accurately what you are trying to accomplish. I submit that if you are not using the value of the expression, there is never a reason to use i++ instead of ++i, because there is never a reason to copy the value of a variable, increment the variable, and then throw the copy away.

Итак, если полученное значение не используется, я бы использовал ++i. Но не потому, что он более эффективен, а потому, что правильно выражает мои намерения.

Не будем забывать, что и другие унарные операторы также являются префиксными. Я думаю, что ++ i - это «семантический» способ использования унарного оператора, в то время как i ++ существует для удовлетворения конкретной потребности (оценка перед добавлением).

TM. 13.08.2009 01:55

Если полученное значение не используется, в семантике присутствует нет разницы: т.е. нет оснований для предпочтения той или иной конструкции.

Eamon Nerbonne 17.09.2010 13:20

Как читатель, я вижу разницу. Итак, как писатель, я покажу свое намерение, выбрав одно из них. У меня просто привычка - пытаться общаться с моими друзьями-программистами и товарищами по команде через код :)

Sébastien RoccaSerra 17.09.2010 13:47

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

JAB 03.07.2012 19:10

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

Lie Ryan 08.08.2012 19:59

Следуя совету Кенига, я кодирую i++ так же, как и i += n или i = i + n, то есть в форме цельглаголобъект, с операндом цель слева от оператора глагол. В случае i++ нет правого объект, но правило все еще применяется, сохраняя цель слева от оператора глагол.

David R Tribble 27.01.2014 22:07

Если вы пытаетесь увеличить i, то i ++ и ++ i правильно выражают ваше намерение. Нет причин предпочитать одно другому. Как читатель, я не вижу разницы, не путаются ли ваши коллеги, когда видят i ++ и думают, может быть, это опечатка и он не хотел увеличивать i?

dan carter 12.02.2014 05:49

Есть разница. Рассмотрим это: 1) int i = 0; 2) int x = i ++; 3) int y = ++ i; При семантическом чтении в строке 2 говорится: «Дайте мне значение i, а затем увеличьте его»; тогда как в строке 3 написано: «Увеличьте i, затем дайте мне его значение».

lfalin 17.09.2014 00:24

@Ifalin, мы говорим об автономных. Никто в здравом уме не использует i++ / ++i как часть заявления.

Pacerier 17.02.2015 09:07

Если вы пытаетесь «увеличить i», гораздо естественнее читать «приращать i», чем читать «i увеличить». Так что даже игнорируя оптимизацию компилятора. Это оптимизирует читаемость.

Andreas 26.05.2016 13:39

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

Используйте то, что имеет наибольший смысл для человека, читающего код.

Я считаю, что неправильно отдавать предпочтение нечетким улучшениям читабельности фактическому повышению эффективности и общей ясности намерений.

noop 21.07.2010 15:53

Мой термин «время чтения программиста» примерно аналогичен «ясности намерений». «Фактический прирост эффективности» часто неизмерим, достаточно близок к нулю, чтобы назвать его нулем. В случае OP, если код не был профилирован, чтобы обнаружить, что ++ i является узким местом, вопрос о том, какой из них быстрее, является пустой тратой времени и мыслительных единиц программиста.

Andy Lester 23.07.2010 03:23

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

noop 24.07.2010 03:17

Вы говорите то, что говорю я. Более важно показать намерение и улучшить читаемость, чем беспокоиться об «эффективности».

Andy Lester 26.07.2010 19:26

Я все еще не могу согласиться. Если удобочитаемость стоит выше в вашем списке приоритетов, возможно, вы выбрали неправильный язык программирования. Основная цель C / C++ - написание эффективного кода.

noop 09.08.2013 01:38

Однако я всегда предпочитаю предварительное приращение ...

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

Итак, для практических целей, вероятно, нет большой разницы между производительностью двух форм. Однако я всегда предпочитаю предварительное приращение, поскольку мне кажется, что лучше прямо выразить то, что я пытаюсь сказать, чем полагаться на оптимизатор, чтобы понять это.

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

Ваша публикация специфична для C++, а вопрос касается C. В любом случае ваш ответ неверен: для пользовательских операторов постинкремента компилятор, как правило, нет может производить столь же эффективный код.

Konrad Rudolph 16.10.2008 15:31

Он заявляет, что «если функция встроена», и это делает его рассуждения правильными.

Blaisorblade 15.01.2009 03:21

Поскольку C не обеспечивает перегрузки оператора, вопрос до и после обработки в значительной степени неинтересен. Компилятор может оптимизировать неиспользуемый временный интервал, используя ту же логику, которая применяется к любому другому неиспользуемому, вычисляемому примитивно значению. См. Выбранный ответ для образца.

Andrew Eidsness 16.01.2009 16:09

@Отметка Несмотря на то, что компилятору разрешено оптимизировать временную копию переменной (на основе стека), а gcc (в последних версиях) это делает, не означает, что компиляторы все всегда будут делать это.

Я только что протестировал его с компиляторами, которые мы используем в нашем текущем проекте, и 3 из 4 не оптимизируют его.

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

Если у вас нет действительно глупой реализации одного из операторов в вашем коде:

Всегда предпочитал ++ i над i ++.

Просто любопытно ... почему вы используете 4 разных компилятора C в одном проекте? Или в одной команде или в одной компании, если на то пошло?

Lawrence Dol 18.01.2013 23:19

При создании игр для консолей каждая платформа имеет собственный компилятор / набор инструментов. В идеальном мире мы могли бы использовать gcc / clang / llvm для всех целей, но в этом мире мы должны мириться с Microsoft, Intel, Metroworks, Sony и т. д.

Andreas 26.01.2013 17:24

Я могу представить ситуацию, когда постфикс медленнее, чем приращение префикса:

Представьте, что процессор с регистром 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 в C.


К деталям.

1. Хорошо известная проблема C++: ++i работает быстрее.

В C++ ++i более эффективен, если и только если i - это какой-то объект с перегруженным оператором приращения.

Почему? В ++i объект сначала увеличивается, а затем может быть передан как константная ссылка любой другой функции. Это невозможно, если выражение - foo(i++), потому что теперь необходимо выполнить приращение до вызова foo(), но старое значение необходимо передать в foo(). Следовательно, компилятор вынужден сделать копию i, прежде чем он выполнит оператор приращения на оригинале. Дополнительные вызовы конструктора / деструктора - плохая часть.

Как отмечалось выше, это не относится к основным типам.

2. Малоизвестный факт: i++май будет быстрее

Если не нужно вызывать конструктор / деструктор, что всегда имеет место в C, ++i и i++ должны быть одинаково быстрыми, верно? Нет. Они практически одинаково быстры, но могут быть небольшие различия, которые большинство других респондентов ошиблись.

Как i++ может быть быстрее? Дело в зависимостях данных. Если значение необходимо загрузить из памяти, необходимо выполнить с ним две последующие операции, увеличивая его на единицу и используя его. С ++i необходимо увеличить приращение перед, значение можно использовать. С i++ использование не зависит от приращения, и ЦП может выполнять операцию использования в параллели для операции приращения. Разница составляет не более одного цикла ЦП, поэтому ею можно пренебречь, но она есть. И все наоборот, чем многие ожидали.

По поводу вашего пункта 2: если ++i или i++ используется в другом выражении, изменение между ними изменяет семантику выражения, поэтому о любом возможном выигрыше / потере производительности не может быть и речи. Если они автономны, то есть результат операции не используется сразу, то любой достойный компилятор скомпилирует его в одно и то же, например инструкцию сборки INC.

Shahbaz 02.08.2015 18:20

@Shahbaz Совершенно верно, но не в этом дело. 1) Несмотря на то, что семантика различна, i++ и ++i могут использоваться взаимозаменяемо почти во всех возможных ситуациях, изменяя константы цикла на единицу, так что они почти эквивалентны в том, что они делают для программиста. 2) Несмотря на то, что обе компилируются в одну и ту же инструкцию, их выполнение отличается для ЦП. В случае i++ ЦП может вычислить приращение в параллели для какой-либо другой инструкции, которая использует то же значение (ЦП действительно это делают!), В то время как с ++i ЦП должен запланировать приращение другой инструкции после.

cmaster - reinstate monica 02.08.2015 22:56

@Shahbaz В качестве примера: if (++foo == 7) bar(); и if (foo++ == 6) bar(); функционально эквивалентны. Тем не менее, второй может быть на один цикл быстрее, потому что сравнение и приращение могут вычисляться ЦП параллельно. Не то чтобы этот единственный цикл имел большое значение, но разница есть.

cmaster - reinstate monica 02.08.2015 23:03

Хорошая точка зрения. Константы часто появляются (а также <, например, против <=) там, где обычно используется ++, поэтому преобразование между ними часто легко возможно.

Shahbaz 03.08.2015 19:14

Мне нравится пункт 2, но он применим только в том случае, если используется значение, верно? Вопрос гласит: «Если полученное значение не используется?», Поэтому это может сбивать с толку.

jinawee 01.01.2019 22:55

@jinawee Если значение ++i или i++ не используется, они полностью эквивалентны и должны компилироваться в один и тот же код. (Если i не является объектом класса, который имеет нестандартные, отдельные операторы приращения до и после).

cmaster - reinstate monica 25.10.2019 15:05

Краткий ответ:

Между 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.Теперь вы, как программист на C, должны отличаться от людей, которые непоследовательно используют префикс в некоторых случаях и постфикс в других случаях, без каких-либо объяснений почему. Это говорит о том, что они не уверены в том, как работает C, или что у них неправильное знание языка. Это всегда плохой знак, он, в свою очередь, предполагает, что они принимают другие сомнительные решения в своей программе, основанные на суевериях или «религиозных догмах».

«Префикс ++ всегда быстрее» - одна из таких ложных догм, распространенных среди потенциальных программистов на C.

Никто не сказал: «Префикс ++ всегда быстрее». Это неправильно процитировано. Они сказали: «Postfix ++ быстрее никогда».

Pacerier 17.02.2015 09:25

@Pacerier Я не цитирую какого-то конкретного человека, а просто распространяю неверное мнение.

Lundin 03.06.2015 09:37

@rbaleksandar C++ == c && ++ c! = c

sadljkfhalskdjfh 07.10.2015 19:42

Я прочитал здесь большую часть ответов и многие комментарии, и я не видел никаких ссылок на экземпляр один, о которых я мог бы подумать, где i++ более эффективен, чем ++i (и, возможно, на удивление, --iбыл более эффективен, чем i--. ). Это для компиляторов C для DEC PDP-11!

PDP-11 имел инструкции сборки для предварительного декремента регистра и постинкремента, но не наоборот. Инструкции позволяли использовать любой регистр «общего назначения» в качестве указателя стека. Поэтому, если вы использовали что-то вроде *(i++), его можно было бы скомпилировать в одну инструкцию по сборке, а *(++i) - нет.

Это, очевидно, очень эзотерический пример, но он обеспечивает исключение, когда постинкремент более эффективен (или я должен сказать был, поскольку в наши дни нет большого спроса на код PDP-11 C).

Очень эзотерически, но очень интересно!

Mark Harrison 19.08.2019 00:08

@daShier. +1, хотя я не согласен, это не так уж эзотерично, по крайней мере, не должно быть. C был разработан совместно с Unix в AT&T Bell Labs в начале 70-х, когда PDP-11 был целевым процессором. В исходном коде Unix этой эпохи пост-инкремент «i ++» более распространен отчасти потому, что разработчики знали, когда присваивается значение «j = i ++» или используется в качестве индекса «a [i ++] = n», код будет немного быстрее (и меньше). Похоже, они привыкли использовать пост-инкремент, если не требовалось pre. Другие учились, читая свой код, и тоже переняли эту привычку.

jimhark 04.10.2019 03:25

68000 имеет ту же функцию, пост-инкремент и пре-декремент поддерживаются аппаратно (как режимы адресации), но не наоборот. Команда Motorola была вдохновлена ​​DEC PDP-11.

jimhark 04.10.2019 03:31

@jimhark, да, я один из тех программистов PDP-11, которые перешли на 68000 и до сих пор используют --i и i++.

daShier 04.10.2019 07:02

Другие вопросы по теме