参考文章
学过计算机底层原理、了解过很多架构设计或者是做过优化
的同学,
应该很熟悉局部性原理。
即便是非计算机行业的人,在做各种调优、提效时也不得不考虑到局部性,
只不过他们不常用局部性一词。
如果抽象程度再高一些,
甚至可以说地球、生命、万事万物都是局部性
的产物,
因为这些都是宇宙中熵分布布局
、局部的熵低
导致的,
如果宇宙中处处熵一致,有的只有一篇混沌。
所以什么是局部性
?
这是一个常用的计算机术语,
是指
处理器在访问某些数据时
短时间内存在重复访问,
某些数据或者位置
访问的概率极大,
大多数时间只访问局部的数据。
基于局部性原理,
计算机处理器在设计时做了各种优化,
比如现代CPU的多级Cache
、分支预测
……
有良好局部性的程序比局部性差的程序运行得更快。
虽然局部性一词源于计算机设计,
但在当今分布式系统
、互联网技术
里也不乏局部性,
比如像用redis
这种memcache
来减轻后端的压力,
CDN
做素材分发减少带宽占用率
……
平均分布
局部性的本质是什么?
其实就是概率的不均等
,
这个宇宙中,
很多东西都不是平均分布
的,
平均分布
是概率论中几何分布的一种特殊形式,
非常简单,
但世界就是没这么简单。
高斯分布
我们更长听到的发布叫做高斯发布
,
同时也被称为正态分布
,
因为它就是正常状态下的概率发布
,
起概率图如下,但这个也不是今天要说的。
99.7% of the data are within 3 standard deviations of the mean
95% within 2 standard deviations
68% within 1 standard deviation
99.7%的数据都在平均值加减3个标准差之内
95%的数据都在平均值加减2个标准差之内
68%的数据都在平均值加减1个标准差之内
μ(mu)
σ(sigma)
泊松分布
其实有很多情况,很多事物有很强的头部集中现象
,
可以用概率论中的泊松分布
来刻画,
这就是局部性
在概率学
中的刻画形式。
上面分别是泊松分布的示意图和概率计算公式,
λ \lambda λ
表示单位时间(或单位面积)内随机事件的平均发生次数,
e
e
e表示自然常数2.71828..
,
k表示事件发生的次数。
可以参考的文章有:
要注意
在刻画局部性
时
λ
\lambda
λ表示不命中高频数据
的频度,
λ
\lambda
λ越小,头部集中现象越明显
。
局部性分类
局部性有两种基本的分类,
时间局部性
和
空间局部性,
按Wikipedia的资料,可以分为以下五类,其实有些就是时间局部性
和空间局部性
的特殊情况。
时间局部性(Temporal locality):
如果某个信息这次被访问,那它有可能在不久的未来被多次访问。
时间局部性是空间局部性访问地址一样时
的一种特殊情况。
这种情况下,可以把常用的数据加cache来优化访存。
空间局部性(Spatial locality):
如果某个位置的信息被访问,那和它相邻的信息也很有可能被访问到。
这个也很好理解,我们大部分情况下代码都是顺序执行,数据也是顺序访问的。
内存局部性(Memory locality):
访问内存时,大概率会访问连续的块,而不是单一的内存地址,
其实就是空间局部性在内存上的体现。
目前计算机设计中,都是以块/页为单位管理调度存储
,其实就是在利用空间局部性来优化性能。
分支局部性(Branch locality)
这个又被称为顺序局部性,计算机中大部分指令是顺序执行,顺序执行和非顺序执行的比例大致是5:1,
即便有if这种选择分支,其实大多数情况下某个分支都是被大概率选中的,
于是就有了CPU的分支预测优化。
等距局部性(Equidistant locality)
等距局部性是指如果某个位置被访问,那和它相邻等距离的连续地址
极有可能会被访问到,它位于空间局部性和分支局部性之间。
举个例子,
比如多个相同格式的数据数组
,你只取其中每个数据的一部分字段,那么他们可能在内存中地址距离是等距的,
这个可以通过简单的线性预测
就预测是未来访问的位置。
实际应用
计算机领域关于局部性非常多的利用,
有很多你每天都会用到,
但可能并没有察觉,
另外一些可能离你会稍微远一些,
接下来我们举几个例子
来深入了解下局部性的应用
。
计算机存储层级结构
上图来自极客时间徐文浩
的《深入浅出计算机组成原理》,
我们以目前常见的普通家用电脑为例 ,
分别说下
上图各级存储的大小
和访问速度
,
数据来源于
https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html
。
从最快的L1 Cache到最慢的HDD,
其两者的访存时间差距
达到了6个数量级,
即便是和内存比较,也有几百倍的差距。
举个例子,
如果CPU在运算时
直接从内存中读取指令和数据,
执行一条指令0.3ns,
然后从内存读下一条指令,等120ns,
这样CPU 99%计算时间
都会被浪费掉。
但就是因为有局部性的存在,
每一层都只有少部分数据会被频繁访问,
我们可以把这部分数据从底层存储
挪到高层存储
,
可以降低大部分的数据读取时间。
可能有些人好奇,
为什么不把L1缓存
做的大点,
像内存那么大,直接替代掉内存,不是性能更好吗?
虽然是这样,但是L1 Cache单位价格
要比内存单位的价格
贵好多(大概差200倍),
有兴趣可以了解下DRAM
和SRAM
。
我们可以通过编写高速缓存友好的代码逻辑
来提升我们的代码性能,
有两个基本方法。
1、让最常见的情况运行的快,
程序大部分的运行
实际都花在少数核心函数
上,
而这些函数把大部分时间都花在少量循环
上,
把注意力放在这些代码上。
2、让每个循环内缓存不命中率
最小。
比如尽量不要列遍历二维数组
。