2013-04-15 7 views
13

Mam pewne dziwne wyniki, których nie mogę wyjaśnić. Wydaje się, że ta liniapowolna wydajność inicjatora wielowymiarowych macierzy

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

jest 4 razy wolniej niż ten jeden

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; 

(i to nie jest nawet biorąc pod uwagę fakt, że w tym przykładzie mogę pominąć te wszystkie = 0 przypisania)

Wiem, że zapętlenie wielowymiarowej tablicy w języku C# może być wolne ze względu na sprawdzanie granic. Ale nie ma tu pętli, nie są wymagane kontrole graniczne, a cała linia inicjalizująca tablicę może zostać rozwiązana podczas kompilacji.

Drugi blok kodu musi jednak najpierw zainicjować tablicę na zero, a następnie nadpisać każdą wartość osobno.
Więc jaki jest problem?

Jaki byłby najlepszy sposób zainicjowania tej tablicy, jeśli wydajność jest problemem?


Użyłem następujący kod do pomiaru wydajności:

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

Efekty:

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

Przyjrzenie się skompilowanej IL kodu jest bardzo różne. ArrayInitializer używa metody RuntimeHelpers.InitializeArray. Ale to jest najlepsze, co mogę zrobić ... Ciekawe pytanie! – Aron

+0

Nigdy nie używasz utworzonej tablicy, więc czy cała alokacja tablicy nie zostanie zoptymalizowana przez kompilator? – Servy

+0

Dlatego ustawiłem tablicę jako statyczną. Jeśli jest to tylko zmienna lokalna, to faktycznie jest zoptymalizowana, ale tylko dla pierwszego przypadku testowego z inicjatorem macierzy. Ale jeśli 'd' jest zmienną statyczną, nie powinno być takiej optymalizacji, ponieważ inny wątek mógłby uzyskać do niej dostęp; a testy czasowe zdają się to potwierdzać. – HugoRune

Odpowiedz