在系统运行中,我们常常忽视cpu local cache miss/hit带来的性能差异,因此,有必要了解cpu local cache的工作机制,为高性能程序的开发提供理论指导。
存储体系结构
通常来说,现代计算机存储结构基本可分为如下等级:
register -> local cache -> memory -> disk storage,其中local cache可能分为L1/L2/L3等多个级别。
参考如下链接可以对各存储器的访问延时有一个直观的印象:
https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html
从中可以发现,访问主存耗时是访问cpu local cache耗时的10倍以上,因此基于时间和空间局部性假设设置cpu local cache能够有效提高程序性能。
local cache加载机制
local cache分为多个cache line,每个line的存储部分的大小为32~64byte,cache line是local cache与memory交换数据的基本单位。
每个cache line包括三个部分:
Valid:当前缓存是否有效
Tag:对应的内存地址
Block:缓存数据部分
local cache与memory映射关系
全关联映射
主存任意一块可以映射到Cache的任意一块
优点:空间利用率高、命中率高
缺点:访问存储器时,每次都要全部查找,速度低,基本不使用
直接映射
主存中的一块只能映射到Cache的一个特定的块中
优点:映射简单,访问速度快
缺点:替换频繁,命中率低
组相连映射
主存根据Cache大小划分多个区,每个区划分成多个组,每个组内划分多个块
Cache划分成多个组,每个组内划分多个块
主存的每个区与Cache直接映射,组内采用全映射方式
缓存一致性协议
既然使用了cache,就存在缓存不一致的问题,local cache通过一套缓存一致性协议保证cache间的一致性,该协议被称为MESI协议。
事实上,MESI是cache line中数据的四种状态,分别如下:
状态 | 描述 |
M(Modified) | 这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 |
E(Exclusive) | 这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。 |
S(Shared) | 这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。 |
I(Invalid) | 这行数据无效。 |
既然有状态,自然就有状态转移,状态转移过程如下:
当前状态 | 事件 | 行为 | 下一个状态 |
I(Invalid) | Local Read | 如果其他Cache没有这份数据,本Cache从该内存中取数据,Cache line状态变成E; 如果其他Cache有这份数据,且状态为M,则将数据更新到内存,本Cache再从内存中取数据,两个Cache的Cache line状态都变成S; 如果其他Cache有这份数据,且状态为S或者E,本Cache从内存中取数据,这些Cache的Cache line状态都变成S。 | E/S |
Local Write | 从内存中取数据,在Cache中修改,状态变成M;如果其他Cache有这份数据,切状态为M,则要先将数据更新到内存; 如果其他Cache有这份数据,则其他Cache的Cache line状态变成1 | M | |
Remote Read | 既然是invalid,别的核的操作与它无关 | I | |
Remote Write | 既然是invalid,别的核的操作与它无关 | I | |
E(Exclusive) | Local Read | 从Cache中取数据,状态不变 | E |
Local Write | 修改Cache的数据,状态为M | M | |
Remote Read | 数据和其他核共用,状态变成了S | S | |
Remote Write | 数据被修改,本Cache line不能再使用,状态变成I | I | |
S(Shared) | Local Read | 从Cache中取数据,状态不变 | S |
Local Write | 修改Cache中的数据,状态变成M,其他核共享的Cache line状态变成I | M | |
Remote Read | 状态不变 | S | |
Remote Write | 数据被修改,本Cache line不能再使用,状态变成I | I | |
M(Modified) | Local Read | 从Cache中取数据,状态不变 | M |
Local Write | 修改Cache中的数据,状态不变 | M | |
Remote Read | 这行数据被写到内存中,使其他核能使用到最新的数据,状态变成S | S | |
Remote Write | 这行数据被写到内存中,使其他核能使用到最新的数据,由于其它核会修改这行数据,状态变成I | I |
状态转移图如下:
false sharing
由前述所知,local cache是以cache line为单位进行加载的,当两个不同cpu core的线程更新的两个不同变量恰好位于同一cache line时,由于MESI协议的作用,线程将频繁地从L3甚至memory更新cache line,导致大量的cache miss,这种现象被称为false sharing,即由于cache line加载机制导致local cache miss的现象。
false sharing如下图所示:
volatile关键字和cas实现机制
volatile解决的可见性问题由前述分析可知基于MESI实现。
cas首先修改local cache并尝试invalidate该cache line,由MESI仲裁后,成功者完成操作,失败者接受该结果,invalidate自身cache line后重新尝试进行操作。
在高性能程序开发中的实际应用
典型应用如disruptor中采用缓存行填充(padding)机制,杜绝false sharing,提升性能。
JDK1.8中ConcurrentHashMap的long类型封装类
参考文章:
https://www.jianshu.com/p/061a5d66ea20