С # сгенерированный оператор IL для ++ - когда и почему префиксная / постфиксная нотация работает быстрее

Поскольку этот вопрос касается оператора приращения и разницы в скорости с префиксной / постфиксной нотацией, я опишу вопрос очень тщательно, чтобы Эрик Липперт не обнаружил его и не разозлил меня!

(дополнительная информация и подробности о почему я спрашиваю, можно найти на http://www.codeproject.com/KB/cs/FastLessCSharpIteration.aspx?msg=3899456#xx3899456xx/ )

У меня есть четыре следующих фрагмента кода : -

(1) Отдельно, Префикс:

    for (var j = 0; j != jmax;) { total += intArray[j]; ++j; }

(2) Отдельно, Постфикс:

    for (var j = 0; j != jmax;) { total += intArray[j]; j++; }

(3) Индексатор, Постфикс:

    for (var j = 0; j != jmax;) { total += intArray[j++]; }

(4) Индексатор, Префикс:

    for (var j = -1; j != last;) { total += intArray[++j]; } // last = jmax - 1

То, что я пытался сделать, это доказать / опровергнуть, есть ли разница в производительности между префиксной и постфиксной нотацией в этом контексте (т. Е. Локальная переменная, не изменяемая, не изменяемая из другого потока и т. д.), и если да, то почему.

Тестирование скорости показало, что:

  • (1) и (2) работают с одинаковой скоростью.

  • (3) и (4) работают с одинаковой скоростью.

  • (3) / (4) на ~ 27% медленнее, чем (1) / (2).

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

Затем я взглянул на сгенерированный IL с помощью Reflector и обнаружил следующее:

  • Количество байтов IL одинаково во всех случаях.

  • .maxstack варьируется от 4 до 6, но я считаю, что он используется только для целей проверки и поэтому не имеет отношения к производительности.

  • (1) и ( 2) генерировал точно такой же IL, поэтому неудивительно, что время было идентичным. Таким образом, мы можем игнорировать (1).

  • (3) и (4) сгенерировали очень похожий код - единственное существенное отличие состоит в расположении кода операции дублирования для учета Результат операции . Опять же, нет ничего удивительного в том, что синхронизация идентична.

Итак, я затем сравнил (2) и (3), чтобы выяснить, что может объяснить разницу в скорости:

  • (2) использует ldloc.0 op дважды (один раз как часть индексатора, а затем позже как часть приращения).

  • (3) использовал ldloc.0, за которым сразу следовала операция dup. (и наличие dup в (3) и (4) нарушает этот шаблон, и поэтому оптимизация не выполняется)

    И дополнительно: Если это правда, то, по крайней мере, для (3), не приведет ли замена dup другим ldloc.0 к повторному введению этого шаблона?

20
задан Manishearth 18 January 2013 в 20:39
поделиться