0

В большом количестве мест в программном обеспечении, которое я пишу, имеются трехмерные массивы short или float, обычно с несколькими миллионами элементов. Данные лучше всего понятны как трехмерный массив, поскольку он описывает значения в обычных местах в пространстве.CLR многоосевое перемещение массива

В другом месте я увидел упоминание о том, что .NET CLR не очень «совершенен», когда дело доходит до прохождения этих массивов, например, при вычислении новых значений и заполнении второго массива одинакового размера и размера. Если это так, почему это так?

Из соображений удобочитаемости я не осевшие на идее использования неровных массивов, но, но если это действительно ответ, то я готов, но:

Чтобы обойти эту проблему, что это было предложено мне что я отформатирую данные как один размерный массив. Например, если мой массив имеет размеры с величинами m, n и o, тогда я бы создал float[m*n*o] вместо float[m,n,o] и написал свой собственный индексатор, чтобы добраться до правильного расположения массива во время обхода.

Специфический случай использования в распараллеливания обхода, например:

Parallel.For(0,m)((x) => for(y=0,y<n,y++) { for(z=0,z<o,z++) doSomething(array(x,y,z)); }); 

Где в одном индексированных случае был бы Parallel.ForEach(myArray, (position) => doSomething(array(Position))) вида, что происходит вместо вложенных for петель.

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

EDIT: Я предоставил свой собственный ответ ниже, основываясь на некоторых тестах времени. Код включен.

+0

Вы бы профиль его, чтобы быть уверенным, но я бы поставил больше времени тратится на копирование данных, то поиск по индексу. –

+0

Каков ваш источник для того, чтобы заявить, что копирование 3D-массива происходит медленно? – Servy

+0

doSomething() лучше быть существенным методом, или шина памяти победит попытки использования потоковой передачи для ускорения кода. В этом случае индексирование массива перестает быть актуальным. У вас есть несколько лошадей, если вы хотите увидеть, какой из них быстрее, вы запустите их. –

ответ

0

Я провел несколько таймингов и обнаружил, что общая производительность является то, что она делает мало или нет разницы:

Я использовал этот код, приведенный ниже. Тайминги я получил, были в основном идентичны в каждом случае:

public partial class Form1 : Form 
{ 
    int ArrayDim1 = 50; 
    int ArrayDim23 = 500; 
    int ParallelSplit = 50; 
    int DoSomethingSize = 100; 

    Double sqRoot = 0; 

    Single[, ,] multidim = null; 
    Single[] singleDim = null; 
    Single[][][] jagged = null; 

    ParallelOptions po = new ParallelOptions() { MaxDegreeOfParallelism = 36 }; 

    public Form1() 
    { 
     InitializeComponent(); 

     multidim = new Single[ArrayDim1, ArrayDim23, ArrayDim23]; 
     for (int x = 0; x < ArrayDim1; x++) 
      for (int y = 0; y < ArrayDim23; y++) 
       for (int z = 0; z < ArrayDim23; z++) 
        multidim[x, y, z] = 1; 
     singleDim = new Single[ArrayDim1 * ArrayDim23 * ArrayDim23]; 
     for (int i = 0; i < singleDim.Length; i++) 
      singleDim[i] = 1; 

     jagged = new Single[ArrayDim1][][]; 

     for (int i = 0; i < ArrayDim1; i++) 
     { 
      jagged[i] = new Single[ArrayDim23][]; 
      for (int j = 0; j < ArrayDim23; j++) 
      { 
       jagged[i][j] = new Single[ArrayDim23]; 
      } 
     } 

    } 

    private void btnGO_Click(object sender, EventArgs e) 
    { 
     int loopcount = 1; 

     DateTime startTime = DateTime.Now; 
     for (int i = 0; i < loopcount; i++) 
     { 
      TestMultiDimArray(multidim); 
     } 
     textBox1.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###"); 

     startTime = DateTime.Now; 
     for (int i = 0; i < loopcount; i++) 
     { 
      TestSingleArrayClean(singleDim); 
     } 
     textBox2.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###"); 

     startTime = DateTime.Now; 
     for (int i = 0; i < loopcount; i++) 
     { 
      TestJaggedArray(jagged); 
     } 
     textBox3.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###"); 
    } 

    public void TestJaggedArray(Single[][][] multi) 
    { 
     Parallel.For(0, ArrayDim1, po, x => 
     { 
      for (int y = 0; y < ArrayDim23; y++) 
      { 
       for (int z = 0; z < ArrayDim23; z++) 
       { 
        DoComplex(); 
        multi[x][y][z] = Convert.ToSingle(Math.Sqrt(123412341)); 
       } 
      } 
     }); 
    } 

    public void TestMultiDimArray(Single[, ,] multi) 
    { 

     Parallel.For(0, ArrayDim1, po, x => 
      { 
       for (int y = 0; y < ArrayDim23; y++) 
       { 
        for (int z = 0; z < ArrayDim23; z++) 
        { 
         DoComplex(); 
         multi[x, y, z] = Convert.ToSingle(Math.Sqrt(123412341)); 
        } 
       } 
      }); 
    } 

    public void TestSingleArrayClean(Single[] single) 
    { 
     Parallel.For(0, single.Length, po, y => 
      { 
       //System.Diagnostics.Debug.Print(y.ToString()); 
       DoComplex(); 
       single[y] = Convert.ToSingle(Math.Sqrt(123412341)); 
      }); 
    } 

    public void DoComplex() 
    { 
     for (int i = 0; i < DoSomethingSize; i++) 
     { 
      sqRoot = Math.Log(101.101); 
     } 
    } 
} 
0

Одна огромная вещь, которую следует учитывать, - это порядок обхода. Кэширование памяти - важная часть современной производительности процессора, а промахи в кеше могут быть (относительно) дорогими. Если вы индексируете массив по длинному измерению, что приводит к пересечению границ кеша, вы можете вызвать частые промахи как часть индексации. Таким образом, порядок, в котором вы указали, важен. Это часто означает, что вы хотите позаботиться о том, как вы выбираете заказывать свои индексы.

Кроме того, при копировании учитывайте, что для множественной индексации требуется вычислить индекс «истина» в базовый блок памяти, используя умножение/добавление. Однако, если вы просто копируете все элементы, вы можете просто увеличить один индекс и получить доступ к каждому элементу без дополнительных вычислений.

Существуют также различные проверки условий, которые возникают при доступе к массивам по индексу (что делает возможным исключение IndexOutOfRangeException), что требует дополнительных проверок при доступе через несколько индексов. Я считаю (хотя я не совсем уверен), что джиттер может иногда оптимизировать доступ к одномерному массиву с помощью простого цикла, проверяя диапазон только один раз, а не на каждую операцию индексирования.

+0

Я индексирую строчный порядок, пересекая измерение последнего порядка внутри циклов второго, а затем первого. –

+0

@Rob Perkins, возможно, стоит профилировать 3d-массив против 1-го массива. Обратите внимание, что в 1-м случае вы также можете сохранить параллельный индекс, когда итерации последнего порядка-первого и просто «i ++» на каждой итерации; это уменьшит количество вычислений на итерацию для фактической индексации массива, в то же время предоставляя вам доступ к x, y, z, если вам это нужно. –

Смежные вопросы