【C#】前置・後置インクリメントの速度差

若干怪文書っぽいですが、タイトルの件を検証したいと思います。

古参のC言語系プログラマーなら一度は聞いたことがあるかもしれませんが、forループのカウンターとして使用する i や j 等の変数のインクリメントは「前置の方が高速だから前置インクリメントを使う説」が C# ではどうなっているか確認していきます。

確認環境

  • VisualStudio2019
  • .NET5 + C#1.0
  • SharpLab(ILの確認)

補足ですが IL2CPP で C# を C++ に変換してコンパイルする行為は本記事の対象外です。

確認するコード

確認するコードは以下の通りです。

public static void TestCase()
{
    for(int i = 0; i < 1000; i++) // (1) 後置インクリメント
    {
        // nop
    }
    
    for(int i = 0; i < 1000; i++) // (2) 前置インクリメント
    {
        // nop
    }
    
    // 以下ついでに
    // シンプルなデクリメントのケース

    // (a) 後置デクリメント
    int a = 10;
    a--;
    
    // (b) 前置デクリメント
    int b = 10;
    --b;
}

これをインクリメントの部分だけILで抜粋すると以下のようになります。

// i++
IL_0007: ldloc.2
IL_0008: ldc.i4.1
IL_0009: add
IL_000a: stloc.2

// ++i
IL_001e: ldloc.s 4
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.s 4

// a--;
IL_0036: ldloc.0
IL_0037: ldc.i4.1
IL_0038: sub
IL_0039: stloc.0

// --a;
IL_003d: ldloc.1
IL_003e: ldc.i4.1
IL_003f: sub
IL_0040: stloc.1

この場合完全に同じですね。とうか前置の方が後置に統一されています。速度差は当然ありません。

次のコードは(まぁこんなコードを書く人はほとんどいないと思いますが)どうでしょう?

int i = 0;
while (i++ < 10)
{
    Console.WriteLine(i);
}

i = 0;
while (++i <= 10)
{
    Console.WriteLine(i);
}
// while (i++ < 10)
IL_000e: ldloc.0
IL_000f: dup
IL_0010: ldc.i4.1
IL_0011: add
IL_0012: stloc.0
IL_0013: ldc.i4.s 10
IL_0015: clt
IL_0017: stloc.1
// sequence point: hidden
IL_0018: ldloc.1
IL_0019: brtrue.s IL_0005

// while (++i <= 10)
IL_0028: ldloc.0
IL_0029: ldc.i4.1
IL_002a: add
IL_002b: dup
IL_002c: stloc.0
IL_002d: ldc.i4.s 10
IL_002f: cgt
IL_0031: ldc.i4.0
IL_0032: ceq
IL_0034: stloc.2
// sequence point: hidden
IL_0035: ldloc.2
IL_0036: brtrue.s IL_001f

これもほぼ同じですね。というか比較前のldloc~stlocは演算子によって dup -> add か add -> dul で順番が違いますが IL ステップ数がほぼ同じなので違うというほどではないと思います(まぁここからレジスタがどうとかキャッシュがというのは何とも言えないですが…

結論

C# では違いなし。

です。というか C# はアプリケーションレイヤーで使用する場合が多いと思いますが、インクリメントの極小の差を気にする前に、高度なライブラリやアプリ自体にチューニングすべき個所が他に存在する可能性が圧倒的に高いのでこの程度の事は気にする必要ないと思いました。

以上です。