配列のパフォーマンスあれこれ

やねうらおさんからC#の配列が(C++と比べて)遅いという話をいただき、色々実験してみました。

発端となったコード

// loop=1024*64
byte[] a = new byte[loop]; 
for(int i=0;i<loop;++i)
{
  for(int j=loop-1;j>=0;--j)
  {
    a[i] = a[j];
  }
}

C#では13〜14秒掛かりますがC++で同等のコードを書くと6〜7秒しか掛かりません。こんなに差があると悔しいのでC++に迫ってみようとあらゆる手を尽くしてみました。(笑)

using System;
using System.Runtime.InteropServices;

unsafe class App {
  delegate void M();

  const int loop = 1024 * 64;

  // 配列とconst定数
  static void ArrayConst() {
    byte a = new byte[loop];
    for(int i=0;i<loop;++i) {
      for(int j=loop-1;j>=0;--j) {
        a[i] = a[j];
      }
    }      
  }

  // fixedポインタとconst定数
  static void FixedConst() {
    byte p = new byte[loop];
    fixed (byte* a = &p[0]) {
      for(int i=0;i<loop;++i) {
        for(int j=loop-1;j>=0;--j) {
          a[i] = a[j];
        }
      }
    }
  }

  // スタック上の配列とconst定数
  static void StatckAllocConst() {
    byte* a = stackalloc byte [loop];
    for(int i=0;i<loop;++i) {
      for(int j=loop-1;j>=0;--j) {
        a[i] = a[j];
      }
    }
  }

  // Unmanagedメモリとconst定数
  static void AllocHGlobalConst() {
    byte* a = (byte*)Marshal.AllocHGlobal(loop);
    for(int i=0;i<loop;++i) {
      for(int j=loop-1;j>=0;--j) {
        a[i] = a[j];
      }
    }
    Marshal.FreeHGlobal*1;
    Test(new M(FixedConst));
    Test(new M(StatckAllocConst));
    Test(new M(AllocHGlobalConst));
    Test(new M(ArrayLocal));
    Test(new M(ArrayLength));
  }
}
/* 結果
ArrayConst        :00:00:13.4218750
FixedConst        :00:00:09.5000000
StatckAllocConst  :00:00:06.8593750
AllocHGlobalConst :00:00:07.2968750
ArrayLocal        :00:00:13.3906250
ArrayLength       :00:00:09.7812500 */

見ての通りstackallocはスタックにメモリを取るだけあって流石に速いです。関数内でしか使用できないのが玉に瑕。fixedは思ったほど速くないです。そして意外なのがLengthプロパティ。fixedに迫るパフォーマンスが出ています。ひょっとして、constなメンバだと最適化が掛からないのかとローカル変数に代入したケースも試してみましたが速くなりません。どうしても不可解だったのでildasmで逆アセンブルしてみるとLengthプロパティはldlen命令になっていました。このldlen命令ですが配列の要素数を返す命令らしいです。ILは配列型を認識していて配列専用の命令を持っているため速いんですね。なのでC#では、Lengthプロパティをアクセスするのが遅いと考えてローカル変数に値をキャッシュするようなことは避けた方が良さそうです。

*1:IntPtr)a); } // 配列とローカル変数 static void ArrayLocal() { byte a = new byte[loop]; int len = loop; for(int i=0;i<len;++i) { for(int j=len-1;j>=0;--j) { a[i] = a[j]; } } } // 配列とLengthプロパティ static void ArrayLength() { byte a = new byte[loop]; for(int i=0;i<a.Length;++i) { for(int j=a.Length-1;j>=0;--j) { a[i] = a[j]; } } } // テスト static void Test(M m) { DateTime t = DateTime.Now; m(); Console.WriteLine("{0}:{1}", m.Method.Name.PadRight(18), DateTime.Now - t); } [STAThread] static void Main(string[] args) { Test(new M(ArrayConst