一个编写良好的计算机程序经常具备良好的局部性,它们倾向于引用最近引用过的数据项附近的数据项,或者最近引用过的数据项自己,这种倾向性,被称为局部性原理。有良好局部性的程序比局部性差的程序运行得更快。git
时间局部性示例github
function sum(arry) { let i, sum = 0 let len = arry.length for (i = 0; i < len; i++) { sum += arry[i] } return sum }
在这个例子中,变量sum在每次循环迭代中被引用一次,所以,对于sum来讲,具备良好的时间局部性chrome
空间局部性示例数组
具备良好空间局部性的程序浏览器
// 二维数组 function sum1(arry, rows, cols) { let i, j, sum = 0 for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { sum += arry[i][j] } } return sum }
空间局部性差的程序性能
// 二维数组 function sum2(arry, rows, cols) { let i, j, sum = 0 for (j = 0; j < cols; j++) { for (i = 0; i < rows; i++) { sum += arry[i][j] } } return sum }
看一下上面的两个空间局部性示例,像示例中从每行开始按顺序访问数组每一个元素的方式,称为具备步长为1的引用模式。
若是在数组中,每隔k个元素进行访问,就称为步长为k的引用模式。
通常而言,随着步长的增长,空间局部性降低。测试
这两个例子有什么区别?区别在于第一个示例是按行扫描数组,每扫描完一行再去扫下一行;第二个示例是按列来扫描数组,扫完一行中的一个元素,立刻就去扫下一行中的同一列元素。code
数组在内存中是按照行顺序来存放的,结果就是逐行扫描数组的示例获得了步长为 1 引用模式,具备良好的空间局部性;而另外一个示例步长为 rows,空间局部性极差。内存
运行环境:get
对一个长度为9000的二维数组(子数组长度也为9000)进行10次空间局部性测试,时间(毫秒)取平均值,结果以下:
所用示例为上述两个空间局部性示例
步长为 1 | 步长为 9000 |
---|---|
124 | 2316 |
从以上测试结果来看,步长为 1 的数组执行时间比步长为 9000 的数组快了一个数量级。
参考资料: