什么是局部性原理
一个优秀的程序、优美的代码,往往具有良好的局部性。那么什么是程序的局部性原理呢?
程序局部性原理:是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域,具体来说,局部性通常有两种形式:时间局部性和空间局部性。
时间局部性:被引用过一次的存储器位置在未来会被多次引用(通常在循环中)。
空间局部性:如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。 这样说过于理论了些,例子是最好说明问题的途径,看下面一段代码:
//求数组元素之和,v为数组名,n为数组大小,
int sum(int *v, int n)
{
int i = 0;
int sum = 0;
for (i=0; i<n; ++i)
{
sum+=v[i];
}
return sum;
}
我们知道,数组的特点是在内存中是向下图一样连续存放的。 根据代码以及局部性定义可知:
对于循环中的sum变量:有良好的时间局部性。因为在for循环结束之前,每次执行循环体都有对 sum 的访问。而 sum 没有空间局部性。因为sum 是标量(也就是说通过 sum 这个地址只能得到一个值)
对于循环体中的 v 变量:有良好的空间局部性。因为数组v是按顺序存放在内存中,每次访问 v[i]总是在 v[i-1] 的下一个位置。而v没有时间局部性,因为在循环体中,每个元素v[i]只会被访问一次。
局部性对程序效率的影响
我们再来看看一个遍历二维数组的例子:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int a[500][500];
//先访问行
void fun_1()
{
int i,j;
for(i=0; i<500; i++)
{for(j=0; j<500; j++){ a[i][j]=i;}
}
}
//先访问列
void fun_2()
{
int i,j;
for(j=0; j<500; j++)
{for(i=0; i<500; i++){ a[i][j]=i;}
}
}
int main()
{
clock_t start, finish;
double duration;
start = clock();
fun_1();
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "fun_1: %f seconds", duration);
start = clock();
fun_2();
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "fun_2: %f seconds", duration) ;
return 0;
}
bogon:dataStructure lizhong$ ./test
fun_1: 0.000624 seconds
fun_2: 0.001193 seconds
上面的例子,fun_1和fun_2都是对一个二维数组进行遍历赋值。在fun_1函数的for循环体中,是以行序为主序对元素进行遍历。也就是说内层循环先访问第一行的元素,然后第二行......,而二维数组在存储器中也是按照行序为主序来进行存储的。也就是说先存储第一行,然后第二行......,如下图所示。本例中存储顺序和访问顺序一致。所以可以该程序对a[][]的引用有良好的空间局部性。
而fun_2函数只是在fun_1的基础上将求和函数中的双重循环的索引i和j调换一下位置,也就是说在对a[][]进行遍历的时候,以列序为主序。即先访问第一列,在访问第二列......,而前面讲了二维数组在存储器中也是按照行序为主序来进行存储;意味着每访问一个元素,就要跳过N个元素才能访问下一个。这种情况下没有良好的空间局部性。
再来看看运行结果,对于同一个数组,具有空间局部性的fun_1函数运行的效率几乎是没有局部性的fun_2函数的提高了一倍,至于为什么有良好局部性的程序有更好的性能,这个和计算机的缓存是息息相关的,将会在后面的文章中介绍。这篇文章且当作后文的铺垫吧。
推荐阅读:
完全整理 | 365篇高质技术文章目录整理 360°全方位解读「缓存」 GitHub 上有哪些一般人也可以用的项目? Github 标星 4w+,如何用 Python 实现所有算法 浅谈什么是递归算法
专注服务器后台技术栈知识总结分享
欢迎关注交流共同进步
码农有道 coding
码农有道,为您提供通俗易懂的技术文章,让技术变的更简单!