What are the differences between a multidimensional array and an array of arrays in C#?
在C中,多维数组
如果有区别,每种方法的最佳用途是什么?
阵列(锯齿状阵列)比多维阵列更快,可以更有效地使用。多维数组有更好的语法。
如果您使用锯齿状和多维数组编写一些简单的代码,然后使用IL反汇编程序检查编译的程序集,您将看到锯齿状(或一维)数组的存储和检索是简单的IL指令,而多维数组的相同操作是始终较慢的方法调用。
考虑以下方法:
1 2 3 4 5 6 7 8 9 | 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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | .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也告诉您,在使用多维数组分析项目时,应该使用交错数组而不是多维数组。
多维数组创建了一个很好的线性内存布局,而交错数组则意味着多个间接级别。
在交错数组中查找值
多维数组在内存中线性布局,实际值通过将索引相乘得到。但是,给定数组
交错数组的
索引多维数组更快。例如,给定这个例子中的多维数组
因此,多维数组分配连续内存块,而锯齿形数组不必是方形的,例如,
性能方面,多维数组应该更快。速度快得多,但由于一个非常糟糕的CLR实现,它们不是。
1 2 3 | 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 |
第一行是交错数组的计时,第二行是多维数组,第三行是多维数组,应该是这样的。程序如下所示,仅供参考,这是运行mono测试的。(Windows时间安排大不相同,主要是由于CLR实现的变化)。
在Windows上,交错数组的时间安排非常优越,这与我自己对多维数组查找应该是什么样子的解释大致相同,请参见"single()"。遗憾的是,Windows JIT编译器真的很蠢,这让性能讨论变得困难,有太多的不一致之处。
这些是我在Windows上得到的时间安排,这里是相同的处理,第一行是锯齿形数组,第二行是多维的,第三行是我自己的多维实现,注意这在Windows上比Mono慢多少。
1 2 3 | 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 |
源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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(); } } |
简单地说,多维数组类似于DBMS中的表。数组(交错数组)允许您让每个元素保存另一个具有相同类型可变长度的数组。
因此,如果您确定数据的结构类似于表(固定的行/列),则可以使用多维数组。锯齿形数组是固定元素,每个元素可以容纳可变长度的数组
例如,psuedocode:
1 2 3 4 5 |
把上面的表格想象成一个2x2的表格:
1
2 1 | 2
3 | 4
1 2 3 4 |
把上面的每一行看作是列数可变的行:
1
2
3 1 | 2 | 3 | 4
11 | 12
21 | 22 | 23
前言:这篇评论是为了解决奥古坦提供的答案,但由于苏的愚蠢的声誉系统,我不能把它张贴在它所属的地方。
您断言一个比另一个慢,因为方法调用不正确。一种比另一种慢,因为边界检查算法更复杂。您可以通过查看(而不是IL)编译的程序集轻松地验证这一点。例如,在我的4.5安装中,访问存储在ECX指向的二维数组中的元素(通过EDX中的指针),索引存储在EAX和EDX中,如下所示:
1 2 3 4 5 6 7 8 9 | 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和JMP,代码几乎可以解析为
另一个复杂的问题是,在许多情况下,现代编译器会在迭代单个维度数组时,对元素访问的嵌套边界检查进行优化。结果就是代码基本上只是在数组的连续内存上前进一个索引指针。多维数组上的简单迭代通常涉及一个额外的嵌套逻辑层,因此编译器不太可能优化操作。因此,尽管访问单个元素的边界检查开销在数组维度和大小方面逐渐分摊到常量运行时,但是测量差异的简单测试用例可能需要花费很多倍的时间才能执行。
我想对此进行更新,因为在.NET核心多维数组中,多维数组比锯齿状数组更快。我运行了John Leidegren的测试,这些是.NET核心2.0预览版2的结果。我增加了维度值,以减少后台应用程序可能产生的影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
我研究了反汇编,这就是我发现的
我无法确定为什么一维阵列仍然比多维阵列快,但我的猜测是这与在CPU上进行的一些优化有关。
多维数组是(n-1)维矩阵。
所以
交错数组只是数组的数组-每个单元格包含一个数组的数组。
所以mda是成比例的,jd可能不是!每个单元格可以包含任意长度的数组!
上述答案中可能已经提到了这一点,但没有明确提及:对于锯齿形数组,可以使用
除了其他答案之外,请注意,多维数组被分配为堆上的一个大而粗的对象。这有一些含义:
我正在分析由ildasm生成的.il文件,以构建用于进行转换的assemblies、类、方法和存储过程的数据库。我遇到了以下问题,这打破了我的分析。
1 2 3 | .method private hidebysig instance uint32[0...,0...] GenerateWorkingKey(uint8[] key, bool forEncryption) cil managed |
《专家.NET 2.0 IL汇编程序》,作者:SergeLidin,Apress,2006年出版,第8章,原始类型和签名,第149-150页。
示例:让
1)
2)
3)
汤姆