boy-learning-thread | 1.1.3 内存屏障和CPU缓存

相关源码:boy-learning-thread
个人博客:http://bruce.bugmakers.club
内容来自《网易微专业 - 高性能编程章节》

CPU 性能优化手段 - 缓存

为了提高程序运行的性能,现代 CPU 在很多方面对程序进行了优化。

例如:CPU高速缓存。尽可能的避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。

CPU 多级缓存

L1 Cache 一级缓存:CPU 第一层高速缓存,分为数据缓存和指令缓存。一般服务器 CPU 的 L1 缓存的容量通常在 32~4096KB。

L2 由于 L1 级高速缓存容量的限制,为了再次提高 CPU 的运算速度,在 CPU 外部放置一高速存储器,即二级缓存。

L3 现在的都是内置的。而它的实际作用即是,L3 缓存的应用可以进一步降低内存延迟,同时提升大数据量计算时处理器的性能。具有较大 L3 缓存的处理器提供更有效的文件系统缓存行为及较短消息和处理器队列长度。一般是多核共享一个 L3 缓存!

cpu 在读取数据时,现在L1中寻找,再从L2中寻找,再从L3中寻找,然后是内存,再后是外存储器。

缓存同步协议

多 CPU 读取同样的数据进行缓存,进行不同的运算之后,最终写入主内存以哪个 CPU 为准?

在这种高速缓存回写的场景下,有一个缓存一致性协议多数 CPU 厂商对他进行了实现。

MESI 协议,他规定每条缓存有个状态位,同时定义了下面四个状态:

  • 修改态(Modified)——此 cache 行已被修改过(脏行),内容已不同于主存,为此 cache 专有;

  • 专有态(Exclusive)——此 cache 行内容同于主存,但不出现与其他 cache 中;

  • 共享态(Shared)——此 cache 行同于主存,但也出现于其他 cache 中;

  • 无效态(Invalid)——此 cache 行内容无效(空行)。

多处理器时,单个 CPU 对缓存中的数据进行了改动,需要通知给其他 CPU。
也就是意味着,CPU 处理要控制自己的读写操作,还要监听其他 CPU 发出的通知,从而保证最终一致。

CPU 性能优化手段 - 运行时指令重排

指令重排场景:当 CPU 写缓存时,发现缓存区块正在被其他 CPU 占用,为了提高 CPU 处理性能,可能将后面的读缓存命令优先执行。

并非随便重排,需要遵守 as-if-serial 语义。

as-if-serial 语义指的是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
编译器,runtime 和处理器都必须遵守 as-if-serial 语义。也就是说,编译器和处理器不会对存在数据依赖关系的操作做重排序。

两个问题

1、CPU 高速缓存下,有一个问题:

缓存中的数据与主内存的数据并不是实时同步的,各 CPU (或 CPU 核心)间缓存的数据也不是实时同步。

在同一个时间点,各 CPU 所看到同一内存地址的数据的值可能是不一致的。

2、CPU 执行指令重排序优化下,有一个问题:

虽然遵守了 as-if-serial 语义,但仅在单 CPU 自己执行的情况下能保证结果正确。

多核多线程中,指令逻辑无法分辨因果关联,可能出现乱序执行,导致程序运行结果错误。

处理方式 —— 内存屏障

处理器提供了两个内存屏障指令(Memory Barrier)用于解决上述两个问题。

写内存屏障(Store Memory Barrier)

在指令后插入 Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

强制写入主内存,这种显示调用,CPU 就不会因为性能问题考虑而去对指令重排。

读内存屏障(Load Memory Barrier)

在指令前插入 Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存中加载数据。

强制读取主内存内容,让 CPU 缓存与主内存保持一致,避免了缓存导致的一致性问题。

小结

本章节内容主要是对后续 JVM 线程安全问题做的铺垫。

同时,也看到了现代 CPU 不断演进,在程序运行优化中做出的努力。

不同 CPU 厂商所付出的人力物力成本,最终体现在不同 CPU 性能差距上。

推荐阅读更多精彩内容