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

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


当前回答

多维数组创建了一个很好的线性内存布局,而锯齿数组意味着几个额外的间接层次。

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

其他回答

使用基于John Leidegren的测试,我使用。net 4.7.2对结果进行了基准测试,这是与我的目的相关的版本,我认为我可以分享。我最初是从dotnet核心GitHub存储库中的这条注释开始的。

随着数组大小的变化,性能似乎有很大变化,至少在我的设置中是这样,1个处理器xeon, 4physical 8logical。

W =初始化一个数组,并将int I * j放入其中。 Wr = do w,然后在另一个循环中设置int x为[i,j]

随着数组大小的增长,多维似乎表现得更好。

Size rw Method Mean Error StdDev Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
1800*500 w Jagged 2.445 ms 0.0959 ms 0.1405 ms 578.1250 281.2500 85.9375 3.46 MB
1800*500 w Multi 3.079 ms 0.2419 ms 0.3621 ms 269.5313 269.5313 269.5313 3.43 MB
2000*4000 w Jagged 50.29 ms 3.262 ms 4.882 ms 5937.5000 3375.0000 937.5000 30.62 MB
2000*4000 w Multi 26.34 ms 1.797 ms 2.690 ms 218.7500 218.7500 218.7500 30.52 MB
2000*4000 wr Jagged 55.30 ms 3.066 ms 4.589 ms 5937.5000 3375.0000 937.5000 30.62 MB
2000*4000 wr Multi 32.23 ms 2.798 ms 4.187 ms 285.7143 285.7143 285.7143 30.52 MB
1000*2000 wr Jagged 11.18 ms 0.5397 ms 0.8078 ms 1437.5000 578.1250 234.3750 7.69 MB
1000*2000 wr Multi 6.622 ms 0.3238 ms 0.4847 ms 210.9375 210.9375 210.9375 7.63 MB

更新:最后两个测试用双[,]代替int[,]。考虑到误差,这种差异显得很显著。对于int,锯齿与md的平均比率在1.53x和1.86x之间,对于双精度,它是1.88x和2.42x。

Size rw Method Mean Error StdDev Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
1000*2000 wr Jagged 26.83 ms 1.221 ms 1.790 ms 3062.5000 1531.2500 531.2500 15.31 MB
1000*2000 wr Multi 12.61 ms 1.018 ms 1.524 ms 156.2500 156.2500 156.2500 15.26 MB

简单地说,多维数组类似于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

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

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.

多维数组创建了一个很好的线性内存布局,而锯齿数组意味着几个额外的间接层次。

在一个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机器上。

看起来性能比率仍然大致相同。我想说,除非你真的很难优化,否则就使用多维数组,因为语法更容易使用。