在C#中的multidimensional array和数组之间有什么区别?

在C#中double[,]multidimensional arraydouble[,]和array-of-arrays double[][]什么区别?

如果有差异,每个人最好用什么?

数组数组(锯齿形数组)比multidimensional array更快,可以更有效地使用。 multidimensional array具有更好的语法。

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

考虑以下方法:

 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 

使用锯齿状数组时,您可以轻松执行行交换和行调整等操作。 也许在某些情况下,使用multidimensional array将会更安全,但是即使是Microsoft FxCop也会告诉您在使用它来分析项目时应该使用锯齿形数组而不是multidimensional array。

multidimensional array创build一个很好的线性内存布局,而锯齿形数组意味着间接的几个额外的级别。

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

一个multidimensional array在存储器中线性排列,实际值是通过乘以索引find的。 但是,给定数组var mult = new int[10,30] ,该multidimensional array的Length属性将返回元素的总数,即10 * 30 = 300。

一个锯齿状数组的Rank属性总是1,但是一个multidimensional array可以有任何级别。 任何数组的GetLength方法都可以用来获取每个维度的长度。 对于此示例中的multidimensional array, mult.GetLength(1)返回30。

索引multidimensional array更快。 例如,在这个例子中给出multidimensional arraymult[1,7] = 30 * 1 + 7 = 37,得到该索引为37的元素。这是一个更好的内存访问模式,因为只涉及一个内存位置,数组的地址。

因此,multidimensional array分配一个连续的内存块,而锯齿状的数组不必是方形的,例如jagged[1].Length不必等于jagged[2].Length ,对任何multidimensional array来说都是如此。

性能

性能方面,multidimensional array应该更快。 速度要快得多,但是由于一个非常糟糕的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 

第一行是锯齿状数组,第二行显示multidimensional array,第三行,那应该是这样。 该程序如下所示,FYI这是testing运行单声道。 (窗口的时间是非常不同的,主要是由于CLR的实施变化)。

在窗口上,锯齿状数组的时序非常优越,与我自己对multidimensional array查找应该是什么样的解释大致相同,请参见“Single()”。 令人遗憾的是,Windows JIT编译器确实很愚蠢,不幸的是这使得这些性能讨论变得困难,存在太多的不一致。

这些是我在窗口得到的时间,在这里同样的交易,第一行是锯齿arrays,第二个多维和第三个我自己的多维的实现,请注意,这是在窗口比单声道慢多less。

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

简单地说,multidimensional array与DBMS中的表类似。
数组数组(锯齿形数组)可以让每个元素保存另一个相同typesvariables长度的数组。

所以,如果你确定数据的结构看起来像一个表(固定的行/列),你可以使用一个multidimensional array。 锯齿数组是固定的元素,每个元素可以容纳一个可变长度的数组

例如Psuedocode:

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

把上面想象成一个2×2表格:

 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 

前言:这个评论是为了解决okutane提供的答案 ,但由于SO的愚蠢的声誉系统,我不能张贴在它所属的地方。

由于方法调用,你断言一个比另一个慢,这是不正确的。 一个是比另一个慢,因为更复杂的边界检查algorithm。 您可以通过查看,而不是在IL上,而是在编译的程序集中轻松validation。 例如,在我的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] 

在这里,你可以看到方法调用没有任何开销。 边界检查只是非常复杂的感谢非零索引的可能性,这是一个function不提供锯齿arrays。 如果我们删除非零情况下的sub,cmp和jmps,代码几乎可以parsing为(x*y_max+y)*sizeof(ptr)+sizeof(array_header) 。 这个计算速度一样快(一个乘法可以用一个移位来代替,因为这是我们select字节的两个幂的全部原因)随机访问一个元素。

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

multidimensional array是(n-1)维matrix。

所以int[,] square = new int[2,2]是方阵2×2, int[,,] cube = new int [3,3,3]是一个立方体matrix3x3。 比例是不需要的。

锯齿的数组只是数组的数组 – 每个单元格包含一个数组的数组。

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

在上面的答案中可能会提到这一点,但并不明确:使用锯齿形数组,您可以使用array[row]来引用整行数据,但是这不适用于multi-d数组。

我想对此进行更新,因为在.NET中,multidimensional array比核心数组快 。 我运行了John Leidegren的testing,这些都是.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上做的最优化

除了其他答案之外,请注意multidimensional array被分配为堆中的一个大型块。 这有一些含义:

  1. 一些multidimensional array将被分配到大对象堆(LOH),在那里它们的等价锯齿arrays对应物将不具有。
  2. GC将需要find一个连续的空闲内存块来分配一个multidimensional array,而一个锯齿状的数组可能能够填补堆碎片造成的空白…这通常不是一个问题在.NET中,因为压实,但是LOH并没有被默认的压缩(你必须要求它,而且你必须每次都要问)。
  3. 如果你只使用锯齿状的数组,那么在问题出现之前,你需要查看<gcAllowVeryLargeObjects>multidimensional array的方法

我正在parsing由ildasm生成的.il文件,以构build用于执行转换的安装程序,类,方法和存储过程的数据库。 我遇到以下,这打破了我的parsing。

 .method private hidebysig instance uint32[0...,0...] GenerateWorkingKey(uint8[] key, bool forEncryption) cil managed 

Serge Lidin,Apress出版的Expert .NET 2.0 IL Assembler一书,2006年出版,第8章,原始types和签名,第149-150页。

<type>[]被称为<type>的Vector,

<type>[<bounds> [<bounds>**] ]被称为<type>的数组

**表示可以重复, [ ]表示可选。

例子:让<type> = int32

1) int32[...,...]是一个未定义的下限和大小的二维数组

2) int32[2...5]是下界2和大小4的一维数组。

3) int32[0...,0...]是下界0和未定义大小的二维数组。

汤姆

Interesting Posts