为什么.NET中的multidimensional array比正常数组慢?

编辑:我向大家道歉。 当我实际上打算说“multidimensional array”时,我使用了“锯齿状数组”这个术语(可以在下面的例子中看到)。 我很抱歉使用不正确的名字。 实际上,我发现参差不齐的数组要比multidimensional array快! 我已经添加了锯齿arrays的测量值。

我正在尝试使用一个 盘陀 今天的multidimensional array,当我注意到它的性能不像我所预期的那样。 使用一维数组和手动计算索引要比使用二维数组快得多(几乎是两倍)。 我用1024*1024数组写了一个testing(初始化为随机值),进行了1000次迭代,在我的机器上得到了以下结果:

 sum(double[], int): 2738 ms (100%) sum(double[,]): 5019 ms (183%) sum(double[][]): 2540 ms ( 93%) 

这是我的testing代码:

 public static double sum(double[] d, int l1) { // assuming the array is rectangular double sum = 0; int l2 = d.Length / l1; for (int i = 0; i < l1; ++i) for (int j = 0; j < l2; ++j) sum += d[i * l2 + j]; return sum; } public static double sum(double[,] d) { double sum = 0; int l1 = d.GetLength(0); int l2 = d.GetLength(1); for (int i = 0; i < l1; ++i) for (int j = 0; j < l2; ++j) sum += d[i, j]; return sum; } public static double sum(double[][] d) { double sum = 0; for (int i = 0; i < d.Length; ++i) for (int j = 0; j < d[i].Length; ++j) sum += d[i][j]; return sum; } public static void Main() { Random random = new Random(); const int l1 = 1024, l2 = 1024; double[ ] d1 = new double[l1 * l2]; double[,] d2 = new double[l1 , l2]; double[][] d3 = new double[l1][]; for (int i = 0; i < l1; ++i) { d3[i] = new double[l2]; for (int j = 0; j < l2; ++j) d3[i][j] = d2[i, j] = d1[i * l2 + j] = random.NextDouble(); } // const int iterations = 1000; TestTime(sum, d1, l1, iterations); TestTime(sum, d2, iterations); TestTime(sum, d3, iterations); } 

进一步的调查表明,第二种方法的IL比第一种方法大23%。 (代码大小68 vs 52.)这主要是由于调用System.Array::GetLength(int) 。 编译器还会发出对Array::Get调用 盘陀 multidimensional array,而它只是简单地调用ldelem数组。

所以我想知道,为什么通过multidimensional array访问比正常数组慢? 我会假定编译器(或JIT)会做类似于我在第一种方法中做的事情,但事实并非如此。

你能否帮我理解为什么这样发生?


更新:遵循Henk Holterman的build议,这里是TestTime的实现:

 public static void TestTime<T, TR>(Func<T, TR> action, T obj, int iterations) { Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < iterations; ++i) action(obj); Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed); } public static void TestTime<T1, T2, TR>(Func<T1, T2, TR> action, T1 obj1, T2 obj2, int iterations) { Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < iterations; ++i) action(obj1, obj2); Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed); } 

下界为0的单维数组与IL( vectorarray IIRC)中的多维或非0下界数组是不同的types。 vector是更简单的工作 – 去元素x,你只是做pointer + size * x 。 对于一个array ,你必须为单维数组做pointer + size * (x-lower bound) ,而对于每一个增加的维度,则需要更多的算术运算。

CLR基本上是针对更为常见的情况进行优化的。

数组边界检查?

单维数组有一个你直接访问的长度成员 – 当编译时,这只是一个内存读取。

multidimensional array需要GetLength(int维)方法调用来处理参数以获取该维度的相关长度。 这不会编译到内存读取,所以你得到一个方法调用,等等

另外,GetLength(int维度)将对参数进行边界检查。

有趣的是,我使用VS2008 NET3.5SP1 Win32在Vista上运行了以下代码,在发布/优化方面的差异几乎无法衡量,而debugging/不使用多色arrays要慢得多。 (我跑了三次testing,以减less第二套的JIT影响。)

  Here are my numbers: sum took 00:00:04.3356535 sum took 00:00:04.1957663 sum took 00:00:04.5523050 sum took 00:00:04.0183060 sum took 00:00:04.1785843 sum took 00:00:04.4933085 

看看第三组数字。 对于我来说,在单维数组中编写所有代码是不够的。

虽然我没有贴出来,但是在Debug / unoptimized中多维与单/锯齿确实有很大的区别。

完整的程序:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace single_dimension_vs_multidimension { class Program { public static double sum(double[] d, int l1) { // assuming the array is rectangular double sum = 0; int l2 = d.Length / l1; for (int i = 0; i < l1; ++i) for (int j = 0; j < l2; ++j) sum += d[i * l2 + j]; return sum; } public static double sum(double[,] d) { double sum = 0; int l1 = d.GetLength(0); int l2 = d.GetLength(1); for (int i = 0; i < l1; ++i) for (int j = 0; j < l2; ++j) sum += d[i, j]; return sum; } public static double sum(double[][] d) { double sum = 0; for (int i = 0; i < d.Length; ++i) for (int j = 0; j < d[i].Length; ++j) sum += d[i][j]; return sum; } public static void TestTime<T, TR>(Func<T, TR> action, T obj, int iterations) { Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < iterations; ++i) action(obj); Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed); } public static void TestTime<T1, T2, TR>(Func<T1, T2, TR> action, T1 obj1, T2 obj2, int iterations) { Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < iterations; ++i) action(obj1, obj2); Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed); } public static void Main() { Random random = new Random(); const int l1 = 1024, l2 = 1024; double[ ] d1 = new double[l1 * l2]; double[,] d2 = new double[l1 , l2]; double[][] d3 = new double[l1][]; for (int i = 0; i < l1; ++i) { d3[i] = new double[l2]; for (int j = 0; j < l2; ++j) d3[i][j] = d2[i, j] = d1[i * l2 + j] = random.NextDouble(); } const int iterations = 1000; TestTime<double[], int, double>(sum, d1, l1, iterations); TestTime<double[,], double>(sum, d2, iterations); TestTime<double[][], double>(sum, d3, iterations); TestTime<double[], int, double>(sum, d1, l1, iterations); TestTime<double[,], double>(sum, d2, iterations); TestTime<double[][], double>(sum, d3, iterations); } } } 

因为multidimensional array只是一个语法糖,因为它实际上只是一个具有一些指数计算魔术的平面数组。 另一方面,锯齿状数组就像一个数组。 使用二维数组,访问一个元素只需要读取一次内存,而使用两级锯齿数组时,则需要读取两次内存。

编辑︰显然原来的海报混合了“锯齿阵”与“multidimensional array”,所以我的推理不完全站。 真正的原因,请查阅Jon Skeet的重炮回答。

锯齿形数组是指类引用(其他数组)的数组,直到叶数组可能是一个基本types的数组。 因此分配给每个其他arrays的内存可以遍布整个地方。

而multidimensional array的内存分配在一个连续的块。

我认为它有一些事情做锯齿数组实际上是数组的数组,因此有两个层次的间接去实际的数据。

我和其他人在这里

我有一个三维数组的程序,让我告诉你,当我将数组移动到二维时,我看到一个巨大的提升,然后我移动到一维数组。

最后,我认为在执行时间里,性能提升了500%以上。

唯一的缺点是增加了复杂性,以找出一维数组中的什么地方,而不是三个数组。

我认为多维更慢,运行时必须检查两个或更多(三维和上)边界检查。

边界检查。 如果“i”小于l1,你的“j”variables可能超过l2。 这在第二个例子中是不合法的