让我们考虑一个矩阵
std::vector<std::vector<int>> matrix;
其中每行具有相同的长度。我会将每个std::vector<int>
称为一列。
为什么使用外部循环比使用内部循环更快地遍历外部维度?
第一个程序:首先迭代列
int sum = 0;
for (int col = 0 ; col < matrix.size() ; col++)
{
for (int row = 0 ; row < matrix[0].size() ; row++)
{
sum += matrix[col][row];
}
}
第二个程序:首先迭代行
int sum = 0;
for (int row = 0 ; row < matrix[0].size() ; row++) // Assuming there is at least one element in matrix
{
for (int col = 0 ; col < matrix.size() ; col++)
{
sum += matrix[col][row];
}
}
这是我的猜测
跳转内存
我可能有一种模糊的直觉,即在内存中跳转比读取连续的内存需要更多的时间,但我认为RAM的内存访问需要恒定的时间。另外,DRAM中没有可移动的部分,我不明白为什么如果连续两个int
读取它们会更快?
总线宽度
int
占用2个字节(尽管可能因数据模型而异)。在具有8字节宽总线的机器中,我可以想象,如果int
s在内存中是连续的,那么4 int
s(取决于数据模型)可以被发送到处理器每个时钟周期,如果它们不是连续的,每个时钟周期只能发送一个int
。
如果是这种情况,那么如果matrix
包含长度为8个字节的long long int
,我们就不会再看到这两个程序之间的任何差异(我还没有测试过它) )。
缓存
我不知道为什么,但我觉得缓存可能是第二个程序速度慢的原因。缓存的影响可能与我刚才谈到的总线大小参数有关。可能只有DRAM中连续的内存可以加载到缓存中,但我不知道为什么会这样。
答案 0 :(得分:4)
是的,它是cache。
有一个奇怪的巧合 1 ,当程序访问内存中的数据时,它们通常会立即或不久后访问附近的数据。
CPU设计人员意识到了这一点,因此设计了缓存,以便一次加载整块内存。
因此,当您访问matrix[0][0]
时,如果不是matrix[0]
的所有剩余内容都与matrix[0][0]
中的单个元素一起被拉入缓存中,那么很可能没有任何内容来自matrix[20]
使其成为缓存。
请注意,这取决于由连续数组组成的矩阵,至少在最后一个维度。如果您正在使用链接列表,那么您可能 2 看不到太多差异,而是无论访问顺序如何都会遇到性能下降。
原因是缓存加载了连续的块。考虑matrix[0][0]
是否指向内存地址0x12340000
。访问它会加载该字节,加上接下来的127个字节进入缓存(具体数量取决于cpu)。所以你在缓存中拥有从0x12340000
到0x1234007F
的每个字节。
在连续数组中,0x12340004
的下一个元素已经在缓存中。但链接列表不是连续的,下一个元素几乎可以在任何地方。如果它超出0x12340000
到0x1234007F
范围,则表示您没有获得任何收益。
1 如果你仔细想想,那真是不奇怪。使用本地堆栈变量?访问相同的内存区域。迭代一维数组?对同一内存区域的大量访问。使用外部循环中的外部维度和内部嵌套循环中的内部数组迭代二维数组?基本上迭代一堆一维数组。
2 你可能会运气好,让你的链表的节点彼此相邻,但这似乎是一个非常不可能的场景。并且你仍然不会在缓存中容纳那么多元素,因为指向下一个元素的指针会占用空间,并且间接会有额外的小性能命中。
答案 1 :(得分:-1)
当Going Column - row时,你的计数是这样的([C] [R])[0] [0] + [0] [1] + [0] [2] ......依此类推。所以你不是在数组的元素之间切换。
当进行行 - 列时,你就像这样计算([C] [R])[0] [0] + [1] [0] + [2] [0]这样你就可以在元素之间切换每次都是数组,因此在DRAM中它需要更长的时间。
2D数组的处理方式如下:new Array {array1,array2,array3};数组内的数组。向下计数数组(C-R)比切换数组和计算同一行(R-C)中的元素要快。
数组是一段内存,所以当你有二维数组并计算(R-C)时,你会在DRAM中跳转,这会更慢。
DRAM中没有机械部件并没关系,跳转会慢一些。示例:SRAM没有机械部件,但比DRAM慢(当然具有更大的尺寸),因为更多的距离可以用于更大尺寸的额外晶体管和电容器。
在阅读完其他答案之后编辑,我想在迭代(C-R)时将整个元素加载到缓存中以便快速访问。但是当进入(R-C)每次将新的数组元素加载到高速缓存中时效率不高或者由于效率低而不可能发生。