我有两个for循环基本上在两个不同的数组中查找(每个数组的峰值大小约为2-4k),并根据这些值在第三个数组中设置一个值。出于一些奇怪的原因,这段代码的性能有两个不同,这取决于我把两个for循环放在哪个顺序。
这是第一次设置。它在我的PC上执行约150毫秒:
public static int[] SchoolMultiplication(int[] a, int[] b, int numberBase)
{
List<double> times = new List<double>();
TimeTest timeTest = new TimeTest();
int aLen = a.Length;
int bLen = b.Length;
int[,] resultMatrix = new int[a.Length + b.Length, aLen];
int[] result = new int[a.Length + b.Length];
timeTest.Start();
for (int horizontalIndex = 0; horizontalIndex < b.Length; horizontalIndex++)
{
for (int verticalIndex = 0; verticalIndex < a.Length; verticalIndex++)
{
resultMatrix[a.Length + b.Length - 1 - verticalIndex - horizontalIndex, verticalIndex] = a[a.Length - verticalIndex - 1] * b[b.Length - horizontalIndex - 1];
}
}
现在,如果我只改变循环的顺序,就像这样
for (int verticalIndex = 0; verticalIndex < a.Length; verticalIndex++)
{
for (int horizontalIndex = 0; horizontalIndex < b.Length; horizontalIndex++)
{
resultMatrix[a.Length + b.Length - 1 - verticalIndex - horizontalIndex, verticalIndex] = a[a.Length - verticalIndex - 1] * b[b.Length - horizontalIndex - 1];
}
}
该方法的总运行时间下降到约400毫秒。循环次序的简单交换如何将性能提高近300%?我想它是某种缓存或指针性能的东西?
答案 0 :(得分:19)
这是数据安排的事情。将内存视为单维数组。这就是事物实际安排在磁盘上的方式(就计算机而言。)因此,在创建多维数组时,当您更改循环顺序时,您将更改数组的遍历方式。你不是按顺序阅读,而是从一个位置跳到另一个位置。
多维数组对您来说是这样的:
就像这样对待电脑。最佳遍历方式的索引如下箭头所示:
因此,当您更改数组循环时,数组的遍历方式如下:
因此,您会获得更多缓存未命中和更差的执行算法。
答案 1 :(得分:4)
地点,地点,数据的位置。来自维基百科(它比我说的要好):
线性数据结构:由于代码包含倾向于通过索引引用数组或其他数据结构的循环,因此经常会出现位置。当相关数据元素被线性排列和访问时,发生顺序局部性,即空间局部性的特殊情况。例如,从基址到最高元素的一维数组中元素的简单遍历将利用内存中数组的顺序局部性。[2]当线性遍历在具有相同结构和大小的相邻数据结构的较长区域上时,出现更一般的等距局部性,并且除此之外,不是整个结构都在进入,而是仅仅是结构的相互对应的相同元素。当矩阵表示为行的顺序矩阵并且要求访问矩阵的单个列时就是这种情况。
答案 2 :(得分:1)
很可能与缓存命中/未命中有关。不同之处在于顺序访问和分散访问,其大小超过一个缓存行的大小。
对于普通的c ++循环,它也有助于使循环向后以在循环上获得一点性能。不确定它如何适合.NET。
答案 3 :(得分:1)
你的直觉是正确的,这是一个缓存问题。下面的@Mike Daniels帖子问题基本上描述了完全相同的问题。第二位代码将获得更多缓存命中。
Fastest way to loop through a 2d array?
但是,我们不应该关心性能吗? :)
答案 4 :(得分:0)
我记得在Code Complete中读过这篇文章。在大多数语言中,数组都是按顺序设置最后一个索引来设置的,所以你在迭代最后一个索引时直接访问一行中的字节,而不是在迭代第一个索引时跳过。
答案 5 :(得分:0)
我还认为数组a和b的相对大小会有所不同。
如果a.length很大且b.length很小,则第二个选项应该更快。 相反,如果a.length很小并且b.length很大,则第一个选项会更快。 问题是避免内循环的设置/拆卸成本。
是的,为什么你有int aLen = a.Length;
然后还直接打电话给.Length?好像你应该选择其中一个。