为什么缓存局部性对阵列性能至关重要?


70

在以下博客中,有一个关于数组相对于链表的优势的声明:

阵列具有更好的缓存局部性,可以在性能上产生很大的不同。

这意味着什么?我不了解缓存局部性如何提供巨大的性能优势。


3
如果您了解了缓存的工作原理,那么您还将了解1)“引用的局部性”是一件好事,并且2)与从列表访问相同的数据相比,从数组访问数据通常更有可能具有良好的“局部性” 。
paulsm4'8

1
值得注意的是,尽管这是事实,但将单链列表与连续分配器组合在一起可能是一项巨大的资产,主要是因为将元素从一个容器传输到另一个容器仅涉及指针逻辑。但是,如果您查看它们的内存布局,则它是连续的,看起来像一个数组,仅具有指向数组中下一个元素的链接,因此它仍然对缓存友好(至少在列表全部重组之前)。

Answers:


107

请参阅我有关时空局部性的答案。

特别是,数组是连续的内存块,因此它们的大块将在首次访问时加载到高速缓存中。这使得访问数组的未来元素相对较快。另一方面,链接列表不一定位于连续的内存块中,并且可能导致更多的高速缓存未命中,从而增加了访问它们的时间。

考虑数组datal_data大型结构的链表的以下可能的内存布局

Address      Contents       | Address      Contents
ffff 0000    data[0]        | ffff 1000    l_data
ffff 0040    data[1]        |   ....
ffff 0080    data[2]        | ffff 3460    l_data->next
ffff 00c0    data[3]        |   ....
ffff 0100    data[4]        | ffff 8dc0    l_data->next->next
                            | ffff 8e00    l_data->next->next->next
                            |   ....
                            | ffff 8f00    l_data->next->next->next->next

如果我们要遍历该数组,则第一次访问ffff 0000将需要我们进入内存以进行检索(CPU周期中非常慢的操作)。但是,在第一次访问之后,阵列的其余部分将位于缓存中,随后的访问将更快。有了链表,第一次访问也ffff 1000将需要我们去记忆。不幸的是,处理器将直接缓存该位置周围的内存,一直到为止ffff 2000。如您所见,这实际上并没有捕获列表中的任何其他元素,这意味着当我们访问access时l_data->next,我们将再次不得不进入内存。


6
请注意,可以通过使用内存池来改善链接列表的位置。但是,仍然存在“下一个”指针占用额外空间的问题。
稻田

1
@paddy提出了一个很好的观点,因为通常这是链接列表的实现方式
brc 2012年

现在我明白了“链接列表中的缓存未命中”的含义。
AKS

那么,链表引用的位置是空间的还是时间的,还是两者都没有?
Shubham

11

通常,使用数组时,您访问彼此靠近的项目。当顺序访问数组时尤其如此。

当您访问内存时,会在不同级别上缓存其中的一部分。 缓存局部性是指连续操作在缓存中并因此更快的可能性。在阵列中,您可以最大化在缓存中进行顺序元素访问的机会。

对于列表,作为反例,不能保证列表中顺序出现的项实际上在内存中彼此排列在一起。这意味着更少的缓存命中次数,并且降低了性能。


但是,这在很大程度上取决于处理器和内存架构。例如,专为面向对象编程而设计的CPU通常并不关心位置,仅仅是因为根据“面向对象”的定义,您无论如何都不能保证位置。
约尔格W¯¯米塔格

@JörgWMittag那么,您是说用OOP语言编写的程序没有有效地使用高速缓存,还是在这种情况下与以过程语言编写的程序相比,高速缓存未命中率更高?
萨拉·帕蒂(Saurabh Patil)'18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.