在c#中,多维数组double[,]和数组的数组double[][]有什么区别?
如果有区别,每一种的最佳用途是什么?
在c#中,多维数组double[,]和数组的数组double[][]有什么区别?
如果有区别,每一种的最佳用途是什么?
当前回答
这可能在上面的回答中提到过,但没有明确地提到:对于锯齿数组,您可以使用array[row]引用整行数据,但这对于多维数组是不允许的。
其他回答
多维数组是(n-1)维矩阵。
所以int[,] square = new int[2,2]是一个方阵2x2, int[,] cube = new int[3,3,3]是一个立方-方阵3x3。比例不是必需的。
交错数组只是数组的数组——每个单元格包含一个数组的数组。
所以MDA是成比例的,JD可能不是!每个单元格可以包含任意长度的数组!
简单地说,多维数组类似于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
更新。net 6:
随着. net 6的发布,我决定是时候重新讨论这个话题了。我重写了new . net的测试代码,并按照每个部分至少运行一秒钟的要求运行它。基准测试是在AMD Ryzen 5600x上完成的。
Results? It's complicated. It seems that Single array is the most performant for smaller and large arrays (< ~25x25x25 & > ~200x200x200) and Jagged arrays being fastest in between. Unfortunately it seems from my testing that multi-dimensional are by far the slowest option. At best performing twice as slow as the fastest option. But! It depends on what you need the arrays for because jagged arrays can take much longer to initialize on 50^3 cube the initialization was roughly 3 times longer than single dimensional. Multi-dimensional was only a little bit slower than single dimensional.
结论?如果您需要快速的代码,请自己在将要运行它的机器上进行基准测试。CPU架构可以完成各个方法的相对性能变化。
数字!
Method name Ticks/Iteration Scaled to the best
Array size 1x1x1 (10,000,000 iterations):
Jagged: 0.15 4.28
Single: 0.035 1
Multi-dimensional: 0.77 22
Array size 10x10x10 (25,000 iterations):
Jagged: 15 1.67
Single: 9 1
Multi-dimensional: 56 6.2
Array size 25x25x25 (25,000 iterations):
Jagged: 157 1.3
Single: 120 1
Multi-dimensional: 667 5.56
Array size 50x50x50 (10,000 iterations):
Jagged: 1,140 1
Single: 2,440 2.14
Multi-dimensional: 5,210 4.57
Array size 100x100x100 (10,000 iterations):
Jagged: 9,800 1
Single: 19,800 2
Multi-dimensional: 41,700 4.25
Array size 200x200x200 (1,000 iterations):
Jagged: 161,622 1
Single: 175,507 1.086
Multi-dimensional: 351,275 2.17
Array size 500x500x500 (100 iterations):
Jagged: 4,057.413 1.5
Single: 2,709,301 1
Multi-dimensional: 5,359,393 1.98
不相信我?自己运行并验证。
注意:常量大小似乎给锯齿状数组一个边缘,但并不足以改变我的基准测试中的顺序。我在一些实例中测量到,当使用用户输入的大小时,锯齿状数组的性能下降了7%,对于单个数组没有差异,对于多维数组的性能下降非常小(~1%或更少)。它在中间最为突出,锯齿状阵列占据主导地位。
using System.Diagnostics;
const string Format = "{0,7:0.000} ";
const int TotalPasses = 25000;
const int Size = 50;
Stopwatch timer = new();
var functionList = new List<Action> { Jagged, Single, SingleStandard, Multi };
Console.WriteLine("{0,5}{1,20}{2,20}{3,20}{4,20}", "Run", "Ticks", "ms", "Ticks/Instance", "ms/Instance");
foreach (var item in functionList)
{
var warmup = Test(item);
var run = Test(item);
Console.WriteLine($"{item.Method.Name}:");
PrintResult("warmup", warmup);
PrintResult("run", run);
Console.WriteLine();
}
static void PrintResult(string name, long ticks)
{
Console.WriteLine("{0,10}{1,20}{2,20}{3,20}{4,20}", name, ticks, string.Format(Format, (decimal)ticks / TimeSpan.TicksPerMillisecond), (decimal)ticks / TotalPasses, (decimal)ticks / TotalPasses / TimeSpan.TicksPerMillisecond);
}
long Test(Action func)
{
timer.Restart();
func();
timer.Stop();
return timer.ElapsedTicks;
}
static void Jagged()
{
for (var passes = 0; passes < TotalPasses; passes++)
{
var jagged = new int[Size][][];
for (var i = 0; i < Size; i++)
{
jagged[i] = new int[Size][];
for (var j = 0; j < Size; j++)
{
jagged[i][j] = new int[Size];
for (var k = 0; k < Size; k++)
{
jagged[i][j][k] = i * j * k;
}
}
}
}
}
static void Multi()
{
for (var passes = 0; passes < TotalPasses; passes++)
{
var multi = new int[Size, Size, Size];
for (var i = 0; i < Size; i++)
{
for (var j = 0; j < Size; j++)
{
for (var k = 0; k < Size; k++)
{
multi[i, j, k] = i * j * k;
}
}
}
}
}
static void Single()
{
for (var passes = 0; passes < TotalPasses; passes++)
{
var single = new int[Size * Size * Size];
for (var i = 0; i < Size; i++)
{
int iOffset = i * Size * Size;
for (var j = 0; j < Size; j++)
{
var jOffset = iOffset + j * Size;
for (var k = 0; k < Size; k++)
{
single[jOffset + k] = i * j * k;
}
}
}
}
}
static void SingleStandard()
{
for (var passes = 0; passes < TotalPasses; passes++)
{
var single = new int[Size * Size * Size];
for (var i = 0; i < Size; i++)
{
for (var j = 0; j < Size; j++)
{
for (var k = 0; k < Size; k++)
{
single[i * Size * Size + j * Size + k] = i * j * k;
}
}
}
}
}
经验教训:总是在基准测试中包含CPU,因为它会有所不同。这次是吗?我不知道,但我怀疑是。
最初的回答:
我想更新一下,因为在。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
我调查了拆解,这是我的发现
[i][j][k] = i * j * k;需要执行34条指令
Multi [i, j, k] = i * j * k;需要执行11条指令
单个[i * dim * dim + j * dim + k] = i * j * k;需要执行23条指令
我无法确定为什么一维数组仍然比多维数组快,但我猜这与CPU上的一些优化有关
多维数组创建了一个很好的线性内存布局,而锯齿数组意味着几个额外的间接层次。
在一个jagged数组中查找值jagged[3][6] var jagged = new int[10][5]的工作方式如下:查找索引为3的元素(这是一个数组),并查找该数组中索引为6的元素(这是一个值)。对于本例中的每个维度,都需要进行额外的查找(这是一种开销很大的内存访问模式)。
多维数组在内存中线性排列,实际值通过将索引相乘得到。然而,给定数组var mult = new int[10,30],该多维数组的Length属性将返回元素的总数,即10 * 30 = 300。
锯齿数组的Rank属性总是1,但多维数组可以有任何Rank。任何数组的GetLength方法都可以用于获取每个维度的长度。对于本例中的多维数组,multi . getlength(1)返回30。
索引多维数组更快。例如,给定这个例子中的多维数组mult[1,7] = 30 * 1 + 7 = 37,获取索引为37的元素。这是一种更好的内存访问模式,因为只涉及一个内存位置,即数组的基址。
多维数组因此分配一个连续的内存块,而锯齿状数组不一定是正方形的,例如jagged[1]。长度不一定等于锯齿状的[2]。长度,这对任何多维数组都成立。
性能
在性能方面,多维数组应该更快。快了很多,但由于一个非常糟糕的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
第一行是锯齿数组的计时,第二行是多维数组第三行,应该是这样的。该程序如下所示,仅供参考,这是测试运行mono。(窗口时间有很大的不同,主要是由于CLR实现的变化)。
在窗口上,锯齿数组的计时非常优越,与我自己对多维数组查找应该是什么样子的解释大致相同,请参阅“Single()”。不幸的是,windows的jit编译器真的很愚蠢,这使得这些性能讨论很困难,有太多的不一致。
这些是我在windows上得到的时间,这里也是一样,第一行是锯齿数组,第二行是多维的,第三行是我自己的多维实现,注意这在windows上比mono慢多了。
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();
}
}
我想从未来开始,我应该在这里补充一些。net 5的性能结果,因为从现在开始,它将成为每个人都使用的平台。
这些测试与约翰·雷德格伦(2009年)使用的测试相同。
我的结果。净5.0.1):
Debug:
(Jagged)
5.616 4.719 4.778 5.524 4.559 4.508 5.913 6.107 5.839 5.270
(Multi)
6.336 7.477 6.124 5.817 6.516 7.098 5.272 6.091 25.034 6.023
(Single)
4.688 3.494 4.425 6.176 4.472 4.347 4.976 4.754 3.591 4.403
Release(code optimizations on):
(Jagged)
2.614 2.108 3.541 3.065 2.172 2.936 1.681 1.724 2.622 1.708
(Multi)
3.371 4.690 4.502 4.153 3.651 3.637 3.580 3.854 3.841 3.802
(Single)
1.934 2.102 2.246 2.061 1.941 1.900 2.172 2.103 1.911 1.911
运行在一个6核3.7GHz AMD Ryzen 1600机器上。
看起来性能比率仍然大致相同。我想说,除非你真的很难优化,否则就使用多维数组,因为语法更容易使用。