Подтвердить что ты не робот

Медленная производительность многомерного инициализатора массива

У меня есть некоторые странные результаты производительности, которые я не могу объяснить. Кажется, что эта строка

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

в 4 раза медленнее, чем этот

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;

(и это даже не связано с тем, что в этом примере я мог бы оставить все эти = 0 назначения)

Я знаю, что цикл по многомерному массиву в С# может быть медленным из-за пограничных проверок. Но здесь нет цикла, не требуется никаких проверок границ, и вся строка инициализации массива может быть разрешена во время компиляции.

Второй блок кода должен сначала инициализировать массив до нуля, а затем перезаписать каждое значение отдельно.
Так в чем проблема?

И каким будет лучший способ инициализировать этот массив, если производительность является проблемой?


Я использовал следующий код для измерения производительности:

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
4b9b3361

Ответ 1

Вот хорошее объяснение инициализаторов массива и почему вы видите такие разные результаты: http://bartdesmet.net/blogs/bart/archive/2008/08/21/how-c-array-initializers-work.aspx

В основном - инициализатор массива включает в себя создание настраиваемых структур, а прямое назначение каждого элемента - просто прямое назначение в стеке и хотя оно быстрее.