C# のポインタアクセス速度の検証

概要

この記事では、 C# で配列の内容をポインタを使ってアクセスする場合の速度と インデクサ(大カッコに添え字)でアクセスする場合の速度を比較した結果を書いています。 まじめなベンチマークではなく、ほぼ遊びです。

はじめに

C# には unsafe ブロックではポインタが利用可能です。 配列のデータを先頭から順に参照していく場合には、 ポインタによるイテレーションがインデックスアクセスよりも高速と私は信じていたので、 「C# で」どのような結果になるのかベンチマークしてみることにしました。

手段

今回は配列要素へアクセスする方法として インデクサ(大カッコと添字)、 列挙子(GetEnumeratorおよびforeach構文)、 ポインタの 3 つを対象としました。 速度を比較するために、 それぞれの方法で同じタスクを完了するのに要する時間を測定、 比較しました。

長さ 50 万の byte 型配列の値を先頭から順番に参照して、 それぞれを別の byte 型変数に値をコピーする

なお、このタスクは 100 回実行してその平均を求めました。 実際の測定に使ったのは次のメソッドです (これに長さ 50 万の配列を 100 回渡した)。

static unsafe void Test( byte[] ary )
{
    byte num;

    // index access
    Console.Write( Stopwatch.GetTimestamp()+"," );
    for( int i=0; i<ary.Length; i++ )
    {
        num = ary[i];
    }
    Console.Write( Stopwatch.GetTimestamp()+"," );

    // foreach
    Console.Write( Stopwatch.GetTimestamp()+"," );
    foreach( byte n in ary )
    {
        num = n;
    }
    Console.Write( Stopwatch.GetTimestamp()+"," );

    // pointer iteration
    fixed( byte* p = ary )
    {
        byte* q = p;
        Console.Write( Stopwatch.GetTimestamp()+"," );
        for( int i=0; i<ary.Length; i++ )
        {
            num = *q++;
        }
        Console.Write( Stopwatch.GetTimestamp()+"," );
    }
    
    Console.WriteLine();
}

結果

私のノート PC (Pentium M 1.8GB, Memory 1GB)での実行結果は次のようになりました。 なおコンパイル時に最適化を有効にしています。

この結果から、どうやらポインタアクセスだけが他より遅いということが分かりました。 ちなみにコンパイル時に最適化を無効にした場合も同じ傾向が出ました。 続いて、これの int 型バージョンを作って実行してみたところ、次のような結果になりました。

こちらも同じように、ポインタアクセスは他の方法よりも遅い結果になりました。

考察

当初の予想とは異なり、「ポインタを使う方法がもっとも遅い」という結果が出てきました。 このことから、 下手にポインタを使うことで逆にパフォーマンスが低下することが起こりうる、 と考えられます。 うーん、少し意外な結果でした。 環境が異なれば結果が異なることもあると思いますが、 「ポインタは高速」 などと安易に考えてはいけないようです。