2013-04-15 38 views
13

私はかなり説明できない奇妙なパフォーマンス結果があります。 多次元配列の初期化のパフォーマンスが遅い

このライン

d = new double[4, 4]{{1, 0, 0, 0}, 
        {0, 1, 0, 0}, 
        {0, 0, 1, 0}, 
        {0, 0, 0, 1},}; 

はこの1つ

d = new double[4, 4]; 
d[0, 0] = 1; d[0, 1] = 0; d[0, 2] = 0; d[0, 3] = 0; 
d[1, 0] = 0; d[1, 1] = 1; d[1, 2] = 0; d[1, 3] = 0; 
d[2, 0] = 0; d[2, 1] = 0; d[2, 2] = 1; d[2, 3] = 0; 
d[3, 0] = 0; d[3, 1] = 0; d[3, 2] = 0; d[3, 3] = 1; 

より4倍遅いようだ(とそれも、この例では、私はこれらすべての= 0の割り当てを残すことができるという事実を考慮していません)

私はc#の多次元配列のループが境界チェックのために遅くなることがあることを知っています。しかし、ここにループはなく、境界チェックは不要であり、コンパイル時に配列初期化ライン全体を解決することができます。

ただし、2番目のコードブロックでは、最初に配列をゼロに初期化してから、各値を個別に上書きする必要があります。
ここで問題は何ですか?

パフォーマンスが問題になる場合は、この配列を初期化するにはどうすればよいでしょうか?


私はパフォーマンスを測定するには、次のコードを使用:

using System; 
using System.Diagnostics; 
class Program 
{ 
    public static double[,] d; // global static variable to prevent the JIT optimizing it away 

    static void Main(string[] args) 
    { 
     Stopwatch watch; 
     int numIter = 10000000; // repeat all tests this often 

     double[,] d2 = new double[4, 4]{{1, 0, 0, 0}, 
             {0, 1, 0, 0}, 
             {0, 0, 1, 0}, 
             {0, 0, 0, 1},}; 

     // ================================================================ 
     // use arrayInitializer: slowest 
     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < numIter; i++) 
     { 
      d = new double[4, 4]{{1, 0, 0, 0}, 
           {0, 1, 0, 0}, 
           {0, 0, 1, 0}, 
           {0, 0, 0, 1},}; 
     } 
     Console.WriteLine("ArrayInitializer: \t{0:0.##########}ms", watch.ElapsedMilliseconds * 1.0/numIter); 

     // ================================================================ 
     // use Array.Copy: faster 
     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < numIter; i++) 
     { 
      d = new double[4, 4]; 
      Array.Copy(d2, d, d2.Length); 
     } 
     Console.WriteLine("new + Array.Copy: \t{0:0.##########}ms", watch.ElapsedMilliseconds * 1.0/numIter); 

     // ================================================================ 
     // direct assignment: fastest 
     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < numIter; i++) 
     { 
      d = new double[4, 4]; 
      d[0, 0] = 1; d[0, 1] = 0; d[0, 2] = 0; d[0, 3] = 0; 
      d[1, 0] = 0; d[1, 1] = 1; d[1, 2] = 0; d[1, 3] = 0; 
      d[2, 0] = 0; d[2, 1] = 0; d[2, 2] = 1; d[2, 3] = 0; 
      d[3, 0] = 0; d[3, 1] = 0; d[3, 2] = 0; d[3, 3] = 1; 
     } 
     Console.WriteLine("direct assignment: \t{0:0.##########}ms", watch.ElapsedMilliseconds * 1.0/numIter); 
    } 
} 

結果:あなたは、このような異なる結果を参照してください理由

ArrayInitializer:  0,0007917ms 
new + Array.Copy:  0,0002739ms 
direct assignment:  0,0002281ms 
+0

コンパイルされたILを見てみると、コードは非常に異なっています。 ArrayInitializerは、RuntimeHelpers.InitializeArrayメソッドを使用します。しかし、それが私がすることができる最高です...興味深い質問! – Aron

+0

作成された配列を使用することはありません。したがって、配列の割り当て全体がコンパイラによって最適化されるわけではありませんか? – Servy

+0

そのため、私は配列をpublic staticにしました。それが単なるローカル変数であれば、実際には最適化されていますが、配列の初期化子を使用した最初のテストケースに対してのみ最適化されます。しかし、もし 'd'が静的変数ならば、別のスレッドがおそらくそれにアクセスすることができるので、そのような最適化は行われません。タイミングテストはこれを確認しているようです。 – HugoRune

答えて

関連する問題