为什么.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( vector
与array
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。 这在第二个例子中是不合法的