Поскольку этот вопрос касается оператора приращения и различий в скорости с префиксной/постфиксной нотацией, я очень тщательно опишу вопрос, чтобы Эрик Липперт не обнаружил его и не раскритиковал меня!
(дополнительную информацию и более подробную информацию о том, почему я спрашиваю, можно найти по адресу 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 (один раз как часть индексатора, а затем как часть приращения).
(3) использовал ldloc.0, за которым сразу последовала дублирующая операция.
Таким образом, соответствующий IL для увеличения j для (1) (и (2)) равен:
// ldloc.0 already used once for the indexer operation higher up
ldloc.0
ldc.i4.1
add
stloc.0
(3) выглядит так:
ldloc.0
dup // j on the stack for the *Result of the Operation*
ldc.i4.1
add
stloc.0
(4) выглядит так:
ldloc.0
ldc.i4.1
add
dup // j + 1 on the stack for the *Result of the Operation*
stloc.0
Теперь (наконец!) к вопросу:
Является ли (2) быстрее, потому что JIT-компилятор распознает шаблон ldloc.0/ldc.i4.1/add/stloc.0
как простое увеличение локальной переменной на 1 и оптимизирует его? (и наличие dup
в (3) и (4) нарушает этот шаблон, поэтому оптимизация пропускается)
И дополнение: если это правда, то, по крайней мере, для (3), не будет ли замена dup
другим ldloc.0
вновь вводить этот шаблон?
intArray[j]
- person Henk Holterman   schedule 20.05.2011!=jmax
вместо<intArray.Length
?<intArray.Length
обычно заставляет оптимизатор понять, что он может не проверять границы массива. - person CodesInChaos   schedule 20.05.2011Debugger.Break();
перед своим кодом, подключите отладчик и получите ассемблерный код. Как сказал Джим, вы не должны запускать отладчик, а должны подключиться позже. - person CodesInChaos   schedule 20.05.2011