この質問は、インクリメント演算子とプレフィックス/ポストフィックス表記の速度の違いに関するものなので、Eric Lippertがそれを発見して私を怒らせないように、質問を非常に注意深く説明します。
(私が尋ねている理由の詳細と詳細については、http://www.codeproject.com/KB/cs/FastLessCSharpIteration.aspx?msg = 3899456#xx3899456xx /を参照してください)
私は次のように4つのコードスニペットを持っています:-
(1)個別、プレフィックス:
for (var j = 0; j != jmax;) { total += intArray[j]; ++j; }
(2)個別、後置:
for (var j = 0; j != jmax;) { total += intArray[j]; j++; }
(3)インデクサー、Postfix:
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)は(1)/(2)よりも約27%遅くなります。
したがって、後置記法自体よりも前置記法を選択することによるパフォーマンス上の利点はないと結論付けています。ただし、操作の結果を実際に使用すると、コードが単純に破棄される場合よりもコードが遅くなります。
次に、Reflectorを使用して生成されたILを調べたところ、次のことがわかりました。
ILバイト数はすべての場合で同じです。
.maxstackは4から6の間で変化しましたが、これは検証目的でのみ使用されるため、パフォーマンスには関係ないと思います。
(1)と(2)はまったく同じILを生成したので、タイミングが同じであることは当然です。したがって、(1)は無視できます。
(3)と(4)は、非常によく似たコードを生成しました。唯一の関連する違いは、操作の結果を説明するためのdupオペコードの配置です。繰り返しますが、タイミングが同じであることについては驚くことではありません。
そこで、(2)と(3)を比較して、速度の違いを説明できるものを見つけました。
(2)ldloc.0 opを2回使用します(1回はインデクサーの一部として、その後は増分の一部として)。
(3)ldloc.0を使用し、直後にdupopを使用しました。
したがって、(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
今(ついに!)質問に:
JITコンパイラはldloc.0/ldc.i4.1/add/stloc.0
、ローカル変数を1ずつインクリメントするだけのパターンを認識し、それを最適化するため、(2)は高速ですか?(そして、dup
(3)と(4)の存在はそのパターンを壊すので、最適化は失われます)
そして補足:これが本当なら、少なくとも(3)については、dup
を別のものに置き換えてldloc.0
そのパターンを再導入しませんか?