2013-04-15 5 views
13

Ich habe einige seltsame Leistungsergebnisse, die ich nicht ganz erklären kann. Es scheint, dass diese Linielangsame Leistung multidimensionaler Array Initialisierer

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

4 mal langsamer ist als diese

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; 

(und das ist nicht einmal die Tatsache bedenkt, dass ich in diesem Beispiel alle jene = 0 Zuweisungen auslassen könnte)

Ich weiß, dass das Schleifen über ein mehrdimensionales Array in C# aufgrund der Grenzüberprüfungen langsam sein kann. Aber es gibt hier keine Schleife, es sind keine Grenzprüfungen erforderlich, und die gesamte Array-Initialisierungslinie kann zur Kompilierungszeit aufgelöst werden.

Der zweite Codeblock muss jedoch zunächst das Array auf Null initialisieren und dann jeden Wert einzeln überschreiben.
Also, was ist das Problem hier?

Und was wäre der beste Weg, um dieses Array zu initialisieren, wenn Leistung ein Problem ist?


verwendete ich den folgenden Code Leistung zu messen:

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); 
    } 
} 

Die Ergebnisse:

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

Betrachtet man kompilierte IL ist der Code sehr, sehr unterschiedlich. Der ArrayInitializer verwendet eine Methode, RuntimeHelpers.InitializeArray. Aber das ist das Beste was ich tun kann ... Interessante Frage! – Aron

+0

Sie verwenden nie das Array, das erstellt wird, wird also nicht die gesamte Array-Zuweisung vom Compiler einfach wegoptimiert? – Servy

+0

Deshalb habe ich das Array statisch gemacht. Wenn es sich nur um eine lokale Variable handelt, wird sie zwar weg optimiert, aber nur für den ersten Testfall mit dem Array-Initialisierer. Aber wenn "d" eine statische Variable ist, sollte es keine solche Optimierung geben, da ein anderer Thread möglicherweise darauf zugreifen könnte; und die Timing-Tests scheinen dies zu bestätigen. – HugoRune

Antwort