Сведения о вопросе

ЧОВИД

22:11, 4th August, 2020

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

Просмотров: 494   Ответов: 13

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



  Сведения об ответе

KOMP

16:35, 28th August, 2020

Резюме: нет.

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


  Сведения об ответе

park

15:55, 20th August, 2020

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

Во-первых, далеко не очевидно , что ++i является более эффективным, чем i++, по крайней мере, когда речь идет о целочисленных переменных.

И :

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

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


  Сведения об ответе

PAGE

19:45, 22nd August, 2020

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


  Сведения об ответе

PIRLO

03:34, 28th August, 2020

Взяв лист у Скотта Мейерса, более эффективный c++ пункт 6: различайте префиксные и постфиксные формы операций инкремента и декремента .

Префиксная версия всегда предпочтительнее постфиксной в отношении объектов, особенно в отношении итераторов.

Причина этого, если посмотреть на схему вызова операторов.

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

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

Глядя на этот пример, легко увидеть, что префиксный оператор всегда будет более эффективным, чем постфиксный. Из-за необходимости временного объекта в использовании постфикса.

Именно поэтому, когда вы видите примеры использования итераторов, они всегда используют префиксную версию.

Но, как вы указываете для int, фактически нет никакой разницы из-за оптимизации компилятора, которая может иметь место.


  Сведения об ответе

padenie

19:23, 26th August, 2020

Вот дополнительное наблюдение, если вы беспокоитесь о микрооптимизации. Декрементирующие циклы могут быть 'possibly' более эффективными, чем инкрементирующие циклы (в зависимости от архитектуры набора команд, например 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. Условная ветвь, зависящая от состояния регистра CPU ( Z==0 ).

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

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


  Сведения об ответе

VCe znayu

21:58, 24th August, 2020

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

Между i++ и ++i никогда не бывает разницы в скорости. Хороший компилятор не должен генерировать разный код в этих двух случаях.

Длинный ответ:

Все остальные ответы не упоминают о том, что различие между ++i и i++ имеет смысл только в том выражении, в котором оно найдено.

В случае с for(i=0; i<n; i++) i++ является единственным в своем собственном выражении: есть точка последовательности Перед i++ и есть еще одна после него. Таким образом, единственный генерируемый машинный код-это "увеличение i на 1", и он четко определен, как это происходит по отношению к rest программы. Поэтому, если вы измените его на префикс ++, это не будет иметь ни малейшего значения, вы все равно получите машинный код "увеличить i на 1 ".

Различия между ++i и i++ имеют значение только в таких выражениях, как array[i++] = x; и array[++i] = x; . Некоторые могут возразить и сказать, что постфикс будет медленнее в таких операциях, потому что регистр, где находится i , должен быть перезагружен позже. Но затем обратите внимание, что компилятор волен упорядочивать ваши инструкции так, как ему заблагорассудится, до тех пор, пока он не делает "break the behavior of the abstract machine", как это называется в стандарте C.

Поэтому, хотя вы можете предположить, что array[i++] = x; переводится в машинный код как:

  • Хранить значение i в регистре A.
  • Адрес массива в регистр В.
  • Добавьте A и B, сохраните результаты в A.
  • В этом новом адресе, представленном A, сохраните значение x.
  • Хранить значение i в регистре a / / неэффективно, потому что дополнительная инструкция здесь, мы уже делали это однажды.
  • Инкрементный регистр А.
  • Зарегистрировать магазин в i .

компилятор может также производить код более эффективно, например:

  • Хранить значение i в регистре A.
  • Адрес массива в регистр В.
  • Добавьте A и B, сохраните результаты в B.
  • Инкрементный регистр А.
  • Зарегистрировать магазин в i . ...
  • // rest кодекса.

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

Таким образом, нет никакой разницы между префиксом и постфиксом ++ в C. Теперь то, что вы как программист C должны быть в курсе, это люди, которые непоследовательно используют префикс в одних случаях и постфикс в других случаях, без какого-либо обоснования почему. Это говорит о том, что они не уверены в том, как работает C, или что они неверно знают язык. Это всегда плохой знак, он, в свою очередь, предполагает, что они принимают другие сомнительные решения в своей программе, основанные на суеверии или "religious dogmas".

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


  Сведения об ответе

lourence

04:22, 3rd August, 2020

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

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


  Сведения об ответе

+-*/

22:55, 22nd August, 2020

Во-первых: разница между i++ и ++i в C году ничтожна.


К деталям.

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

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

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

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

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

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

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


  Сведения об ответе

ASER

08:14, 19th August, 2020

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

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

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

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

Alwas предпочитают ++i над i++.


  Сведения об ответе

nYU

21:30, 18th August, 2020

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

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


  Сведения об ответе

FAriza

08:54, 6th August, 2020

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

Представьте себе, что процессор с регистром A используется в качестве аккумулятора, и это единственный регистр, используемый во многих инструкциях (некоторые небольшие микроконтроллеры на самом деле таковы).

Теперь представьте себе следующую программу и их перевод в гипотетический assembly:

Инкремент префикса:

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;).


  Сведения об ответе

davran

18:58, 26th August, 2020

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

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

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

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


  Сведения об ответе

LAST

15:08, 27th August, 2020

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

myArray[i++] = "hello";

против

myArray[++i] = "hello";

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться