在c#中,多维数组double[,]和数组的数组double[][]有什么区别?

如果有区别,每一种的最佳用途是什么?


当前回答

更新。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上的一些优化有关

其他回答

交错数组是数组的数组,或者每一行包含一个自己的数组的数组。

这些数组的长度可以不同于其他行的长度。

声明和分配数组的数组

与常规多维数组相比,锯齿数组声明的唯一不同之处在于,我们不只有一对括号。对于锯齿状数组,每个维度都有一对括号。我们这样分配它们:

int [] [] exampleJaggedArray; jaggedArray = new int[2][]; jaggedArray[0] = new int[5]; jaggedArray[1] = new int[3];

初始化数组的数组

int[][] exampleJaggedArray = { New int[] {5,7,2}, New int[] {10,20,40}, 新的int[] {3,25} };

内存分配

锯齿数组是引用的聚合。锯齿状数组不直接包含任何数组,而是有指向它们的元素。大小是未知的,这就是为什么CLR只保留对内部数组的引用。在我们为锯齿状数组的一个数组元素分配内存之后,引用开始指向动态内存中新创建的块。

变量exampleJaggedArray存储在程序的执行堆栈中,并指向动态内存中的一个块,该块包含对内存中其他三个块的三个引用序列;它们每个都包含一个整数数组——锯齿数组的元素:

数组的数组(锯齿数组)比多维数组更快,可以更有效地使用。多维数组有更好的语法。

如果你使用交错数组和多维数组写一些简单的代码,然后用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也告诉我们,当您使用锯齿数组来分析您的项目时,应该使用锯齿数组而不是多维数组。

除了其他答案之外,请注意,多维数组被分配为堆上的一个大块对象。这有一些含义:

Some multidimensional arrays will get allocated on the Large Object Heap (LOH) where their equivalent jagged array counterparts would otherwise not have. The GC will need to find a single contiguous free block of memory to allocate a multidimensional array, whereas a jagged array might be able to fill in gaps caused by heap fragmentation... this isn't usually an issue in .NET because of compaction, but the LOH doesn't get compacted by default (you have to ask for it, and you have to ask every time you want it). You'll want to look into <gcAllowVeryLargeObjects> for multidimensional arrays way before the issue will ever come up if you only ever use jagged arrays.

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

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

交错数组只是数组的数组——每个单元格包含一个数组的数组。

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

这可能在上面的回答中提到过,但没有明确地提到:对于锯齿数组,您可以使用array[row]引用整行数据,但这对于多维数组是不允许的。