由于这个问题是关于增量运算符和前缀/后缀表示法的速度差异,我将非常仔细地描述这个问题,以免 Eric Lippert 发现它并激怒我!
(有关我为什么要问的更多信息和更多详细信息,请访问http://www.codeproject.com/KB/cs/FastLessCSharpIteration.aspx?msg=3899456#xx3899456xx/ 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
我试图做的是证明/反驳在此上下文中前缀和后缀表示法之间是否存在性能差异(即局部变量,因此不是易失性的,不能从另一个线程更改等),如果有,为什么会这样。
速度测试表明:
因此,我得出的结论是,选择前缀表示法相对于后缀表示法本身并没有性能优势。然而当操作结果如果实际使用了它,那么这会导致代码比简单地丢弃它要慢。
然后我使用 Reflector 查看了生成的 IL,发现了以下内容:
IL 字节数在所有情况下都是相同的。
.maxstack 在 4 和 6 之间变化,但我相信它仅用于验证目的,因此与性能无关。
(1) 和 (2) 生成完全相同的 IL,因此时序相同也就不足为奇了。所以我们可以忽略(1)。
(3) 和 (4) 生成非常相似的代码 - 唯一相关的区别是 dup 操作码的定位以解释操作结果。同样,时间相同也就不足为奇了。
因此,我随后比较了 (2) 和 (3),以找出导致速度差异的原因:
因此,(1)(和(2))中 j 递增的相关 IL 为:
// 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
重新引入该模式?