Поскольку этот вопрос касается оператора приращения и разницы в скорости с помощью префикса / постфикса, я опишу вопрос очень тщательно, чтобы Эрик Липперт не обнаружил его и не разжег меня!
(дополнительная информация и более подробная информация о причинахЯ прошу, можно найти на 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% медленнееhan (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
Теперь (наконец!) К вопросу:
Is (2)) быстрее, потому что JIT-компилятор распознает шаблон ldloc.0/ldc.i4.1/add/stloc.0
как простое увеличение локальной переменной на 1 и ее оптимизацию?(и наличие dup
в (3) и (4) нарушает этот шаблон, и поэтому оптимизация пропущена)
И дополнительно: если это так, то, по крайней мере, для (3),не заменит ли dup
другим ldloc.0
повторный ввод этого паттерна?