CPU 性能优化的方式:
- 使用缓存
- 运行时指令重排
使用缓存
为了提高程序的性能,现代 CPU 在很多方面对程序进行了优化,如:CPU 高速缓存,尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。
多级缓存:
- L1 Cache(一级缓存)是 CPU 第一层高速缓存,分为数据缓存和指令缓存。一般服务器 CPU 的 L1 缓存的容量通常在 32-4096 kb
- L2 由于 L1 级高速缓存容量的限制,为了再次提高 CPU 的运算速度,在 CPU 外部放置一高速存储器,即二级缓存
- L3 现在都是内置的,用于进一步降低内存延迟,同时提升大数据量计算时处理器的性能,具有较大 L3 缓存的处理器提供更有效的文件系统缓存行为及较短消息和处理器队列长度,一般是多核共享一个 L3 缓存
CPU读取数据的顺序:CPU 在读取数据时,先在 L1 中寻找,再从 L2 寻找,再从 L3 寻找,然后是内存,再后是外存储器。
缓存一致性协议
缓存一致性协议出现的原因:
多个线程并发访问一个共享变量时,这些线程的执行处理器上的高速缓存各自都会保留一份共享变量的副本,这带来一个问题,一个处理器对共享变量进行修改,其他处理器如何察觉到该更新并做出适当反应,以确保后续处理器读取到这个共享变量时可以读取到这个更新。这就是缓存一致性问题,其本质就是防止读脏数据和读取到更新的数据
缓存一致性协议:MESI(Modified Exclusive Shared Invalid),类似于 java 中的 RentrantReadWriteLock 读写锁,针对同一地址的读内存操作是并发的,针对同一地址的写操作是独占的
MESI 协议,规定每条缓存有个状态位,定义了如下四个状态:
- 修改态:Modified,此 cache 行已经被修改过(脏行),内容已经不同于主内存,此状态为 cache 专有
- 专有态:Exclusive,此 cache 行内容等同于主存,但不出现在其他 cache 中
- 共享态:Shared,此 cache 行内容等同于主存,但也出现在其他 cache 中
- 无效态:Invalid,此 cache 行内容无效(空行)
多处理器时,单个 CPU 对缓存中的数据进行了改动,需要通知其他 CPU,即 CPU 处理要控制自己的读写操作,还要监听其他 CPU 发出的通知,从而保证最终一致。
运行时指令重排
指令重排场景:当 CPU 写缓存时发现缓存块正在被其他 CPU 占用,为了提高 CPU 处理性能,可能将后面的读缓存命令优先执行。
指令重排需要遵循 as-if-serial 语义,即不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序执行的结果不能被改变。编译器、runtime、处理器都必须遵循 as-if-serial 语义。即编译器和处理器不会对存在数据依赖关系的操作作重排序。
**
CPU优化产生的问题
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 缓存与主内存保持一致,避免了缓存导致的一致性问题