在计算机科学中,访问局部性,也称为局部性原理,是取决于存储器访问模式频繁访问相同值或相关存储位置的现象的术语。访问局部性有两种基本类型——时间和空间局部性。时间局部性是指在相对较小的持续时间内对特定数据和/或资源的重用。空间局部性是指在相对靠近的存储位置内使用数据元素。当数据元素被线性地排列和访问时,例如遍历一维数组中的元素,发生顺序局部性,即空间局部性的特殊情况。
局部性的类型
有几种不同类型的访问局部性:
时间局部性
如果在某一点时访问了存储器的特定位置,则很可能在不久的将来将再次访问相同的位置。在对相同存储器位置的相邻访问之间存在时间接近性。在这种情况下,通常努力将访问过的数据的副本存储在可以被更快访问的特殊存储器中。时间局部性是空间局部性的特殊情况,即当预期位置与当前位置相同时。
空间局部性
如果特定存储位置在特定时间被访问,则很可能在不久的将来访问附近的存储位置。在这种情况下,通常尝试猜测当前访问周围的区域的大小和形状,对于该区域,值得准备更快的访问。
内存局部性
显式与存储器相关的空间局部性。
分支局部性
如果在空间-时间坐标空间中路径的预期部分只有几个可能的替代方案。这是当指令循环具有简单结构,或者小的条件分支指令系统的可能结果被限制到只有一小部分可能性的情况。分支局部性通常不是空间局部性,因为少数几个可能性可以彼此远离。
等距局部性
它处于空间局部性和分支局部性之间。考虑以等距模式访问位置的循环,即空间 - 时间坐标空间中的路径是虚线。在这种情况下,简单的线性函数可以预测在不久的将来将访问哪个位置。
为了受益于非常频繁出现的时间和空间局部性,大多数信息存储系统是分级的;见下文。等距局部性通常由处理器的各种不平凡的增量指令支持。对于分支局部性的情况,当代处理器具有复杂的分支预测器,并且在该预测的基础上,处理器的存储器管理器尝试收集和预处理似合理的替换的数据。
局部性的原因
局部性有几个原因。这些原因是某些方面要实现的目标或接受的情况。以下原因不是不相交的;事实上,下面的列表从最一般的情况到特殊情况:
可预测性
事实上,局部性只是计算机系统中一种可预测的行为。
程序结构
局部性通常因为创建计算机程序的方式而发生,用于处理可决定的问题。通常,相关数据存储在存储器中的附近位置。计算中常见的一种模式涉及几个项目的处理,一次一个。这意味着如果进行大量处理,则将访问单个项目多次,从而导致时间局部性。此外,移动到下一项意味着将读取下一项,导致空间局部性,因为存储器位置通常被批量地读取。
线性数据结构
局部性通常因为代码包含循环,倾向于通过索引访问数组或其他数据结构。当相关数据元素被线性地排列和访问时,发生顺序局部性,即空间局部性的特殊情况。例如,从基地址到最高元素的一维数组中的元素的简单遍历将利用存储器中数组的顺序局部性。当线性遍历在具有相同结构和大小的相邻数据结构的较长区域上,访问每个结构的相互对应的元素而不是整个结构时,发生更一般的等距局部性。这是当矩阵被表示为行的顺序矩阵并且需要访问矩阵的单个列时的情况。
内存层次结构的效率
虽然
随机存取存储器使程序员能够在任何时间在任何地方读取或写入,但在实践中,等待时间和吞吐量会受到高速缓存的效率的影响,这通过增加访问局部性来改进。访问局部性差导致缓存抖动和缓存污染,为了避免它,具有弱局部性的数据元素可以从缓存旁路。
一般局部性
如果大多数时间大部分的访问聚集成簇,并且如果该簇系统的形状可以被良好地预测,则其可以用于性能优化。有几种方法可以利用优化技术从局部性获益。常见的技术有:
增加访问局部性
这通常在软件方面实现。
利用访问局部性
这通常在硬件方面实现。时间和空间局部性可以通过分层存储硬件来资本化。等距局部性可以由处理器的适当专用指令使用,这种可能性不仅是硬件的责任,也是软件的,其结构是否适于编译调用所讨论的专门指令的二进制程序。分支局部性是一种更复杂的可能性,因此需要更多的开发工作,但是对于这种局部性的未来探索有比其他更大的空间。
分层存储器
分层存储器是一种硬件优化,它利用空间和时间局部性的优点,并且可以在存储器层级的几个级别上使用。分页显然受益于时间和空间局部性。高速缓存是利用时间局部性的简单示例,因为它是特别设计的,更快但是更小的存储器区域,通常用于保持最近访问的数据和最近引用的数据接近的数据,这可能导致潜在的性能提高。
高速缓存中的数据元素不一定对应于在空间上接近主存储器的数据元素;然而,数据元素一次被带入高速缓存的一个高速缓存行。这意味着空间局部性同样重要:如果一个元素被引用,几个相邻元素也将被带入高速缓存。最后,时间局部性在最低级别上起作用,因为非常紧密地访问结果可以保存在机器寄存器中。一些编程语言(例如C)允许程序员建议某些变量保存在寄存器中。
数据局部性是常规程序的典型存储器访问特征(尽管存在许多不规则的存储器访问模式)。它使分层存储器布局有利可图。在计算机中,存储器被划分为层次结构以便加速数据访问。存储器层级的较低级别倾向于较慢,但是较大。因此,如果程序使用存储器,同时它被缓存在存储器层级的上层级中,则程序将实现更大的性能,并且避免将其他数据带入层级的上层,这将置换将来即将使用的数据。这是一个理想,有时不能实现。
典型的存储器层次结构(访问时间和缓存大小是用于讨论的2013年使用的典型值的近似值;层次结构中的实际值和实际数量级别不同):
·CPU寄存器(8-256个寄存器) - 立即访问,具有处理器最内核的速度
·L1 CPU caches(32 KiB到512 KiB) - 快速访问,每个内核专有的最内存总线的速度
·L2 CPU caches(128 KiB到24 MiB) - 稍慢的访问速度,内核总线之间共享的
内存总线速度·L3 CPU caches(2 MiB至32 MiB) - 甚至更慢的访问速度,同一处理器的更多内核之间共享的内存总线速度
·主要物理内存(RAM)(256 MiB到64 GiB) - 慢速访问,速度受到主板上处理器和内存模块之间的空间距离和通用硬件接口的限制
·磁盘(虚拟内存,文件系统)(1 GiB到256 TiB) - 速度非常慢,由于计算机主板和磁盘设备之间的数据通道较窄,外部软件协议需要在慢硬件接口的顶部
·远程内存(如其他计算机或互联网)(实际上无限制) - 速度从非常慢到极慢
现代机器倾向于将较低存储器的块读取到存储器层级的下一级中。如果这取代了所使用的存储器,则操作系统尝试预测哪些数据将被访问最少(或最近)并将其向下移动到存储器层级。预测算法倾向于简单地降低硬件复杂性,尽管它们变得更复杂。
矩阵乘法
一个常见的例子是矩阵乘法:
for i in 0..n
for j in 0..m
for k in 0..p
C[i][j] = C[i][j] + A[i][k] * B[k][j];
通过切换j和k的循环顺序,大矩阵乘法中的加速变得显着,至少对于在最后一维中放置连续数组元素的语言。这不会改变数学结果,但它提高了效率。在这种情况下,“大”意味着大约每个矩阵中多于100,000个元件,或足够的可寻址存储器,使得矩阵将不适合L1和
L2高速缓存。
for i in 0..n
for k in 0..p
for j in 0..m
C[i][j] = C[i][j] + A[i][k] * B[k][j];
这种加速的原因是在第一种情况下,A [i] [k]的读取在高速缓存中(因为k索引是连续的,最后一维),但是B [k] [j]所以在B [k] [j]上存在高速缓存未命中惩罚。 C [i] [j]是不相关的,因为它可以从内部循环中析出。在第二种情况下,C [i] [j]的读取和写入都在高速缓存中,B [k] [j]的读取在高速缓存中,并且A [i] [k]出内循环。因此,第二示例在内部循环中没有高速缓存未命中惩罚,而第一示例具有高速缓存惩罚。
在2014年的处理器上,第二种情况比第一种情况快五倍,当用C编写并用gcc -O3编译时。 (仔细检查反汇编代码显示,在第一种情况下,gcc使用SIMD指令,而在第二种情况下,它不会,但是缓存代价比SIMD增益差得多)
在上述示例中,也可以通过使用称为阻塞的技术来改进时间局部性。较大的矩阵可以被划分为均匀大小的子矩阵,使得较小的块可以在存储器中被参考(乘法)几次。
for (ii = 0; ii < SIZE; ii += BLOCK_SIZE)
for (kk = 0; kk < SIZE; kk += BLOCK_SIZE)
for (jj = 0; jj < SIZE; jj += BLOCK_SIZE)
for (i = ii; i < ii + BLOCK_SIZE && i < SIZE; i++)
for (k = kk; k < kk + BLOCK_SIZE && k < SIZE; k++)
for (j = jj; j < jj + BLOCK_SIZE && j < SIZE; j++)
C[i][j] = C[i][j] + A[i][k] * B[k][j];
提供上述解决方案的时间局部性是因为块在移动之前可以被使用多次,使得其更少地移入和移出存储器。改进了空间局部性,因为具有连续存储器地址的元件趋向于一起被拉到存储器层级。
参考书目
· P.J. Denning, The Locality Principle, Communications of the ACM, Volume 48, Issue 7, (2005), Pages 19–24
· P.J. Denning, S.C. Schwartz, Properties of the working-set model, Communications of the ACM, Volume 15, Issue 3 (March 1972), Pages 191-198