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

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

for(int i = 0; i < 10; i++) { ... }

и

for(int i = 0; i < 10; ++i) { ... }

или компилятор способен оптимизировать таким образом, чтобы они были одинаково быстрыми в случае, когда они функционально эквивалентны?

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

Ответ 1

В этом случае нет разницы в сгенерированном промежуточном коде для ++ я и я ++. Учитывая эту программу:

class Program
{
    const int counter = 1024 * 1024;
    static void Main(string[] args)
    {
        for (int i = 0; i < counter; ++i)
        {
            Console.WriteLine(i);
        }

        for (int i = 0; i < counter; i++)
        {
            Console.WriteLine(i);
        }
    }
}

Сгенерированный код IL для обеих петель одинаковый:

  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  // Start of first loop
  IL_0002:  ldc.i4.0
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0010
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_000c:  ldloc.0
  IL_000d:  ldc.i4.1
  IL_000e:  add
  IL_000f:  stloc.0
  IL_0010:  ldloc.0
  IL_0011:  ldc.i4     0x100000
  IL_0016:  blt.s      IL_0006
  // Start of second loop
  IL_0018:  ldc.i4.0
  IL_0019:  stloc.0
  IL_001a:  br.s       IL_0026
  IL_001c:  ldloc.0
  IL_001d:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0022:  ldloc.0
  IL_0023:  ldc.i4.1
  IL_0024:  add
  IL_0025:  stloc.0
  IL_0026:  ldloc.0
  IL_0027:  ldc.i4     0x100000
  IL_002c:  blt.s      IL_001c
  IL_002e:  ret

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

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

Ответ 2

А... Откройте снова. ОК. Здесь сделка.

ILDASM - это начало, но не конец. Ключ: что будет генерировать JIT для кода сборки?

Здесь вы хотите сделать.

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

Вот что не очевидно. Компилятор С# генерирует некоторые последовательности MSIL, которые не являются оптимальными во многих ситуациях. JIT он настроил для решения этих проблем и причуд с других языков. Проблема: только "причуды", которые кто-то заметил, были настроены.

Вы действительно хотите сделать образец, который имеет свои реализации, чтобы попытаться вернуться к основному (или где угодно), Sleep() или что-то там, где вы можете прикрепить отладчик, а затем снова запустить подпрограммы.

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

OK. Поэтому, как только код запускается один раз (т.е.: JIT сгенерировал код для него), затем присоедините отладчик во время сна (или что-то еще). Затем посмотрите на x86/x64, который был сгенерирован для двух подпрограмм.

Моя кишка говорит мне, что если вы используете ++ i/i ++, как вы описали, то есть: в автономном выражении, где результат rvalue не используется повторно, разницы не будет. Но не будет ли забавно идти, чтобы узнать и увидеть все аккуратные вещи!:)

Ответ 3

Ребята, ребята, "ответы" для C и С++.

С# - другое животное.

Используйте ILDASM для просмотра скомпилированного вывода, чтобы проверить, есть ли разница MSIL.

Ответ 4

У вас есть конкретный фрагмент кода и релиз CLR? Если это так, сравните его. Если нет, забудьте об этом. Микро-оптимизация и все такое... Кроме того, вы даже не можете быть уверены, что разная версия CLR даст тот же результат.

Ответ 5

Если вы задаете этот вопрос, вы пытаетесь решить неправильную проблему.

Первый вопрос: "Как повысить удовлетворенность клиентов моим программным обеспечением, заставив его работать быстрее?" и ответ почти никогда не "использует ++ я вместо я ++" или наоборот.

Из сообщения Coding Horror " Оборудование дешево, программисты дорогие":

Правила оптимизации:
Правило 1: Не делайте этого. Правило 2 (только для экспертов): Не делайте этого еще. - M.A. Джексон

Я читаю правило 2, которое означает "сначала написать чистый, чистый код, который соответствует вашим потребностям клиента, а затем ускорить его, когда он слишком медленный". Очень маловероятно, что ++i vs. i++ будет решением.

Ответ 6

Как показал Jim Mischel , компилятор будет генерировать идентичный MSIL для двух способов записи цикла for.

Но это так: нет оснований размышлять о JIT или выполнять измерения скорости. Если две строки кода генерируют идентичные MSIL, они не только будут выполняться одинаково, но и фактически идентичны.

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

Ответ 7

В дополнение к другим ответам может быть разница, если ваш i не является int. В С++, если это объект класса, у которого есть операторы ++() и ++(int), перегруженные, это может иметь значение и, возможно, побочный эффект. Производительность ++i должна быть лучше в этом случае (в зависимости от реализации).

Ответ 8

В соответствии с этим ответом я ++ использует одну инструкцию CPU больше, чем ++ i. Но независимо от того, приводит ли это к разнице в производительности, я не знаю.

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

Ответ 9

  static void Main(string[] args) {
     var sw = new Stopwatch(); sw.Start();
     for (int i = 0; i < 2000000000; ++i) { }
     //int i = 0;
     //while (i < 2000000000){++i;}
     Console.WriteLine(sw.ElapsedMilliseconds);

Среднее значение из 3-х прогонов:
для я ++: 1307 для ++ i: 1314

в то время как с я ++: 1261 а c++ i: 1276

Это Celeron D в 2,53 ГГц. Каждая итерация заняла около 1.6 циклов процессора. Это означает, что ЦП выполнял более 1 команды за каждый цикл или что компилятор JIT разворачивал циклы. Разница между я ++ и ++ я составляла всего 0,01 цикла ЦП на итерацию, вероятно, вызванная службами ОС в фоновом режиме.