当前位置:  开发笔记 > 编程语言 > 正文

C#中多维数组和数组数组之间有什么区别?

如何解决《C#中多维数组和数组数组之间有什么区别?》经验,为你挑选了7个好方法。

C#中多维数组double[,]和数组数组之间有什么区别double[][]

如果有差异,每个人的最佳用途是什么?



1> okutane..:

阵列阵列(锯齿状阵列)比多维阵列更快,可以更有效地使用.多维数组具有更好的语法.

如果使用锯齿状和多维数组编写一些简单的代码然后使用IL反汇编程序检查编译的程序集,您将看到从锯齿状(或单维)数组中存储和检索是简单的IL指令,而多维数组的相同操作是方法总是慢的调用.

请考虑以下方法:

static void SetElementAt(int[][] array, int i, int j, int value)
{
    array[i][j] = value;
}

static void SetElementAt(int[,] array, int i, int j, int value)
{
    array[i, j] = value;
}

他们的IL将如下:

.method private hidebysig static void  SetElementAt(int32[][] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldelem.ref
  IL_0003:  ldarg.2
  IL_0004:  ldarg.3
  IL_0005:  stelem.i4
  IL_0006:  ret
} // end of method Program::SetElementAt

.method private hidebysig static void  SetElementAt(int32[0...,0...] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  ldarg.3
  IL_0004:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_0009:  ret
} // end of method Program::SetElementAt

使用锯齿状阵列时,您可以轻松执行行交换和行调整大小等操作.也许在某些情况下,多维数组的使用会更安全,但即使是Microsoft FxCop也会告诉您,当您使用它来分析项目时,应该使用锯齿状数组而不是多维数组.


多维数组在逻辑上应该更有效,但JIT编译器的实现却不是.上面的代码没有用,因为它没有在循环中显示数组访问.
我知道这是一个老问题,只是想知道CLR是否已经针对多维数组进行了优化,因为这个问题被问到了.
@John,自己衡量,不做假设.
@Henk Holterman - 请参阅下面的答案,可能是这样的情况,在Windows上,锯齿状阵列速度很快,但必须意识到这完全是CLR特有的,而不是像单声道...
@John:我的第一反应是,但我错了 - 请参阅Hosams的详细信息.
@AnthonyNichols,没有做过任何优化.它仍然像2009年的答案一样有效.
@arthur C#编译器不进行优化,而JIT进行。查看IL不会告诉您它是如何优化的。

2> John Leidegr..:

多维数组创建了一个漂亮的线性内存布局,而锯齿状数组则暗示了几个额外的间接层.

jagged[3][6]在锯齿状数组中var jagged = new int[10][5]查找值的工作方式如下:在索引3处查找元素(这是一个数组)并在该数组中查找索引6处的元素(这是一个值).对于这种情况下的每个维度,还有一个额外的查找(这是一种昂贵的内存访问模式).

多维数组在内存中线性排列,实际值通过将索引相乘得到.但是,给定数组var mult = new int[10,30],该Length多维数组的属性返回元素的总数,即10*30 = 300.

Rank锯齿状数组的属性始终为1,但多维数组可以具有任何等级.GetLength任何数组的方法都可用于获取每个维度的长度.对于此示例中的多维数组,mult.GetLength(1)返回30.

索引多维数组更快.例如,假设此示例中的多维数组mult[1,7]= 30*1 + 7 = 37,则获取该索引37处的元素.这是更好的内存访问模式,因为只涉及一个内存位置,这是数组的基址.

因此,多维数组分配连续的内存块,而锯齿状数组不必是正方形,例如jagged[1].Length不必相等jagged[2].Length,这对于任何多维数组都是如此.

性能

性能方面,多维数组应该更快.更快,但由于CLR实施非常糟糕,他们不是.

 23.084  16.634  15.215  15.489  14.407  13.691  14.695  14.398  14.551  14.252 
 25.782  27.484  25.711  20.844  19.607  20.349  25.861  26.214  19.677  20.171 
  5.050   5.085   6.412   5.225   5.100   5.751   6.650   5.222   6.770   5.305 

第一行是锯齿状阵列的时序,第二行是多维数组,第三行是应该如何.该程序如下所示,FYI测试运行单声道.(窗口时序差异很大,主要是由于CLR实现的变化).

在窗口上,锯齿状阵列的时序非常优越,与我自己对多维数组查找的解释大致相同,请参阅'Single()'.可悲的是,Windows JIT编译器真的很愚蠢,不幸的是这使得这些性能讨论变得困难,存在太多的不一致.

这些是我在windows上获得的时间,在这里同样处理,第一行是锯齿状数组,第二行是多维,第三行是我自己的多维实现,请注意这与Windows相比在Windows上慢了多少.

  8.438   2.004   8.439   4.362   4.936   4.533   4.751   4.776   4.635   5.864
  7.414  13.196  11.940  11.832  11.675  11.811  11.812  12.964  11.885  11.751
 11.355  10.788  10.527  10.541  10.745  10.723  10.651  10.930  10.639  10.595

源代码:

using System;
using System.Diagnostics;
static class ArrayPref
{
    const string Format = "{0,7:0.000} ";
    static void Main()
    {
        Jagged();
        Multi();
        Single();
    }

    static void Jagged()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var jagged = new int[dim][][];
            for(var i = 0; i < dim; i++)
            {
                jagged[i] = new int[dim][];
                for(var j = 0; j < dim; j++)
                {
                    jagged[i][j] = new int[dim];
                    for(var k = 0; k < dim; k++)
                    {
                        jagged[i][j][k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }

    static void Multi()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var multi = new int[dim,dim,dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        multi[i,j,k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }

    static void Single()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var single = new int[dim*dim*dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        single[i*dim*dim+j*dim+k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }
}


@supercat:C#中的多维数组存储在[row-main order](https://en.wikipedia.org/wiki/Row-major_order)中,交换订阅的顺序会因为你要访问而变慢内存不连续.BTW所报告的时间不再准确,我得到的多维数组的速度几乎是锯齿状数组(在最新的.NET CLR上测试)的两倍,这应该是它应该如何...
但是你的时间似乎太小了(几毫秒).在此级别,您将受到系统服务和/或驱动程序的干扰.让你的测试更大,至少需要一两秒钟.
我知道这有点迂腐,但我不得不提到这不是Windows vs Mono,而是CLR vs Mono.你有时似乎混淆了那些.这两者并不等同; Mono也适用于Windows.
@JohnLeidegren:多维数组在索引一个维度而不是另一个维度时工作得更好的事实已经被理解了半个世纪,因为只有一个特定维度不同的元素将连续存储在内存中,并且具有多种类型的内存(过去访问连续项目比访问远程项目更快.我认为在.net中,应该通过最后一个下标来获得最佳结果索引,这是你正在做的事情,但是在任何情况下测试交换下标的时间都可以提供信息.
尝试自己计时,看看两者是如何表现的.在.NET中,锯齿状数组更加优化.它可能与边界检查有关,但无论原因如何,时间和基准清楚地表明锯齿状阵列比多维阵列更快.

3> shahkalpesh..:

简单地说,多维数组类似于DBMS中的表.
Array of Array(锯齿状数组)允许您让每个元素保持另一个相同类型的可变长度的数组.

因此,如果您确定数据结构看起来像一个表(固定行/列),则可以使用多维数组.锯齿状数组是固定元素,每个元素可以包含可变长度的数组

例如Psuedocode:

int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;

将上述视为2x2表:

1 | 2
3 | 4
int[][] jagged = new int[3][]; 
jagged[0] = new int[4] {  1,  2,  3,  4 }; 
jagged[1] = new int[2] { 11, 12 }; 
jagged[2] = new int[3] { 21, 22, 23 }; 

可以想到以上每行具有可变列数:

 1 |  2 |  3 | 4
11 | 12
21 | 22 | 23


这是决定使用什么时真正重要的事情..不是这个速度的东西..当你有一个正方形阵列时,速度可能会成为一个因素.

4> Eglin..:

前言:此评论旨在解决okutane提供的答案,但由于SO愚蠢的声誉系统,我无法将其发布到它所属的位置.

由于方法调用,你断言一个比另一个慢,这是不正确的.一个比另一个慢,因为更复杂的边界检查算法.您可以通过查看而不是IL,但是在编译的程序集中轻松验证这一点.例如,在我的4.5安装中,访问存储在ecx指向的二维数组中的元素(通过edx中的指针),其中索引存储在eax和edx中,如下所示:

sub eax,[ecx+10]
cmp eax,[ecx+08]
jae oops //jump to throw out of bounds exception
sub edx,[ecx+14]
cmp edx,[ecx+0C]
jae oops //jump to throw out of bounds exception
imul eax,[ecx+0C]
add eax,edx
lea edx,[ecx+eax*4+18]

在这里,您可以看到方法调用没有任何开销.由于可能存在非零索引,因此边界检查非常复杂,这是锯齿阵列无法提供的功能.如果我们删除非零情况下的sub,cmp和jmps,代码几乎可以解析(x*y_max+y)*sizeof(ptr)+sizeof(array_header).这个计算速度一样快(一个乘法可以用一个移位代替,因为这就是我们选择字节大小为两位幂的全部原因),就像随机访问一个元素一样.

另一个复杂因素是,在很多情况下,现代编译器会在迭代单维数组时优化嵌套边界检查元素访问.结果是代码基本上只是将索引指针推进到数组的连续内存上.对多维数组进行简单的迭代通常涉及额外的嵌套逻辑层,因此编译器不太可能优化操作.因此,即使访问单个元素的边界检查开销在数组维度和大小方面分摊到常量运行时,测量差异的简单测试用例可能需要花费很长时间才能执行.



5> adsamcik..:

我想对此进行更新,因为在.NET Core中,多维数组比锯齿状数组更快.我运行了John Leidegren的测试,这些是.NET Core 2.0预览2的结果.我增加了维度值,使得后台应用程序的任何可能影响都不太明显.

Debug (code optimalization disabled)
Running jagged 
187.232 200.585 219.927 227.765 225.334 222.745 224.036 222.396 219.912 222.737 

Running multi-dimensional  
130.732 151.398 131.763 129.740 129.572 159.948 145.464 131.930 133.117 129.342 

Running single-dimensional  
 91.153 145.657 111.974  96.436 100.015  97.640  94.581 139.658 108.326  92.931 


Release (code optimalization enabled)
Running jagged 
108.503 95.409 128.187 121.877 119.295 118.201 102.321 116.393 125.499 116.459 

Running multi-dimensional 
 62.292  60.627  60.611  60.883  61.167  60.923  62.083  60.932  61.444  62.974 

Running single-dimensional 
 34.974  33.901  34.088  34.659  34.064  34.735  34.919  34.694  35.006  34.796 

我查看了拆卸,这就是我发现的

jagged[i][j][k] = i * j * k; 需要执行34条指令

multi[i, j, k] = i * j * k; 需要执行11条指令

single[i * dim * dim + j * dim + k] = i * j * k; 需要执行23条指令

我无法确定为什么单维阵列仍然比多维更快但我的猜测是它与CPU上的一些优化有关



6> abatishchev..:

多维数组是(n-1)维数矩阵.

所以,int[,] square = new int[2,2]是方阵2x2的,int[,,] cube = new int [3,3,3]是一个立方体-方阵3x3的.不需要比例.

锯齿状数组只是数组的数组 - 每个单元格包含一个数组的数组.

所以MDA是成比例的,JD可能不是!每个单元格可以包含任意长度的数组!



7> lznt..:

这可能在上面的答案中已经提到过,但没有明确说明:使用锯齿状数组可以array[row]用来引用整行数据,但这不适用于多维数组.

推荐阅读
刘美娥94662
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有