一个编写良好的计算机程序倾向于展示出良好的局部性(locality)。也就是,它们倾向于引用的数据项邻近于其他最近引用过的数据项,或者邻近于最近自我引用过的数据项。这种倾向性,被称为局部性原理(principle of locality),是一个持久的概念,对硬件和软件系统的设计都有着极大影响。
局部性通常有两种形式:时间局部性(temporal locality)和空间局部性(spatial locality)。在一个具有良好时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来被再被多次引用。在一个具有良好空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置。
程序员应该理解局部性原理,因为一般而言,有良好局部性的程序比局部性差的程序运行得更快。现代计算机系统的各个层次,从硬件到操作系统,到应用程序,它们的设计都利用了局部性。在硬件层,局部性原理允许计算机设计者通过引入成为高速缓存存储器的小而快速的存储器来保存最近被引用的指令和数据项,从而提高对主存的访问速度。在操作系统级,局部性原理允许系统使用主存作为虚拟地址空间最近被引用的高速缓存。类似地,操作系统用主存来缓存磁盘文件系统中最近被使用的磁盘块。局部性原理在应用程序的设计中也扮演着重要的角色,例如,Web浏览器将最近被引用的文档放在本地磁盘上,利用的就是时间局部性。大量的Web服务器将最近被请求的文档放在前端磁盘高速缓存中,这些缓存能满足对这些文档的请求,而不需要服务器的任何干涉。
6.2.1 对程序数据引用的局部性
int sumvec(int v[N]) { int i = 0, sum = 0; for(i=0; i<N; i++) { sum += v[i]; } return sum; }
上述代码中,变量sum在每次循环迭代中被引用一次,因此,对于sum来说,有好的局部性;另一方面,因为sum是标量,没有空间局部性。
对于向量v的元素,按照它们存储在存储器中的顺序,被顺序读取,因此,对于v,函数有很好的空间局部性,但是时间局部性很差,因为每个向量元素只被访问一次。因为对于循环体内的每个变量,这个函数要么有好的空间局部性,要么有好的时间局部性,因此,我们可以断定sumvec函数有良好的局部性。
int sumarrayros(int v[M][N]) { int i = 0, j=0; sum = 0; for(i=0; i<M; i++) for(j=0; j<N; j++) { sum += v[i][j]; } return sum; }
上述代码内,数组v的元素都是按照步长1来访问的,因此具有很好的空间局部性;(数组元素是按照行顺序存储的)
int sumarrayros(int v[M][N]) { int i = 0, j=0; sum = 0; for(j=0; j<M; i++) for(i=0; i<N; j++) { sum += v[i][j]; } return sum; }
上述代码中,数组v的元素都是按照步长N来访问的,因此其局部性就很差;
6.2.2 局部性小结
1.重复引用同一个变量的程序有良好的时间局部性;
2.对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。在存储器中以大步长跳来跳去的程序空间局部性会很差;
3.对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。