CPU 缓存行
几个基本概念
主存 约 60-80 纳秒
QPI 总线传输 (between sockets, not drawn) 约 20ns
L3 cache 约 40-45 cycles, 约 15ns
L2 cache 约 10 cycles, 约 3ns
L1 cache 约 3-4 cycles, 约 1ns
寄存器 1 cycle
L1、L2 在 CPU 内部,L3 在主板上,越往上 CPU 到达的时间越短,CPU 内部的数据是独立的,CPU 外部的数据是所有 CPU 共享的。
CPU 的数据不一致问题产生原因假如主存里有 x、y 两个数据,它们会先被加载到 L3 缓存里,让在加载到 L1、L2 缓存,但是 L1、L2 是在 CPU 内部的,那就有可能 CPU1 把 x 加载到缓存修改成 1,CPU2 把 x 加载到缓存修改成 2,产生 CPU 之间的数据不一致问题,也就是 CPU1 修改数据 CPU2 不知道。
解决方案
方案一:总线锁。由于 L2 是通过使用总线与 L3 通讯的,所有当 CPU1 访问 L3 是可以对总线加锁,不允许其他 CPU 访问;缺点:效率偏低
方案二:缓存锁。各种各样的一致性协议,例如英特尔 CPU 使用了 MESI 一致性协议,标记成不同的状态,不同的状态进行不同的处理;缺点:对于不能进入缓存的数据无法加锁,现在的 CPU 是总线锁 + 缓存锁一起完成的数据一致性
缓存块(行)的概念程序局部性原理如果访问内存中的一个数据 A,那么很有可能接下来再次访问到,同时还很有可能访问与数据 A 相邻的数据 B,这分别叫做时间局部性和空间局部性。
时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。
空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
cpu cache 读取过程 CPU Cache 的数据是从内存中读取过来的,以一小块一小块读取数据的,而不是按照单个数组元素来读取数据的,在 CPU Cache 中的,这样一小块一小块的数据,称为 Cache Line (缓存块)。当 CPU 从内存里读取一个 4 个字节数据时,不会只把这 4 个字节读进来,而是会把这 4 个字节之后的很多的数据一起读进来,这就是缓存行,现在缓存行的大小多数是 64 个字节。
伪共享问题假设有内存相邻的数据想 x、y,CPU1 要修改 x,CPU 要修改 y,CPU1 读取 x 时会把 x、y 都读进来,CPU1 改完要通知其他 CPU,CPU1 会把整个缓存行数据都通知一遍,这时 CPU2 虽然不用 x 但 y 被通知了,需要重新在读取 y 所在的缓存行,CPU2 改完 y 后也会把整个缓存行数据都通知一遍,这时 CPU1 虽然不用 y 但 y 被通知了也需要重新读取缓存行,这就产生了位于同一缓存行的两个不同数据,被两个不同 CPU 锁定,产生互相影响问题 ,也就是伪共享问题
可见性:
1)线程开始时会从主内存将数据读到缓存中,如果不是线程可见的话,此线程不会读到其他线程对数据的更改;
2)对数据加上 volatile 修饰,即可达到线程可见,各线程会立即读到对数据的更改;
3)如果 volatile 修饰引用类型,则引用的内部字段不能保证线程可见;
4)缓存行:根据空间局部性原理,从主存是按块读取数据进缓存,这一块就叫缓存行,一个缓存行是 64 字节;根据缓存一致性协议,如果两个线程修改的内容存在于一个缓存行的话,会互相干扰,影响效率, 所以可以前后各填充一定的空来保证有效数据一定在独立的缓存行来提升效率;
5)jdk1.8 中注解 @contended 保证它修饰的变量独占缓存行,前提是关闭 jvm 对它的限制 RestrictContended。
6)缓存行越大、局部空间效率越高、但读取时间越慢,缓存行越小、局部空间效率越低,但读取时间越快
版权声明: 本文为 InfoQ 作者【红袖添香】的原创文章。
原文链接:【http://xie.infoq.cn/article/63f9c90fea181dbf88d882603】。文章转载请联系作者。
评论