CPU缓存一致性相关

前言

  1. 参考/导流:
    1. 小林coding-2.4 CPU 缓存一致性
    2. 小林coding-分析伪共享的问题
  2. 作为Java并发-volatile的前置知识。
    1. 一定程度上以面试为指导,总结最重要的东西,这里不是完整的总结知识,具体使用中遇到问题请查阅参考博客。
  3. 一致性问题面试的时候,问的是很多的。
    1. CPU一致性。
    2. 分布式一致性。
  4. 阅读建议:
    1. 如果基础较弱,建议结合参考博客阅读,这里主要是争对个人情况做的一些笔记,总结重要内容。

遇到的问题——提出问题,方便今后学习的时候提高注意力

  1. 内存管理:
    1. 数据写入时,如何定位数据所对应的Cache Block?
  2. 数据更新时会不会写到L2 Cache,何时写到L2 Cache?

CPU Cache的数据写入

前言

  1. CPU Cache的结构:
    1. Cache高速缓存内嵌在CPU中。
    2. 由多个Cache Line组成的。
    3. Cache Line是CPU从内存读取数据的基本单位。
    4. Cache Line由 各种标志(Tag)+数据块(DataBlock)组成。
  2. 在多核心的处理器里,每个核心都有各自的L1/L2 Cache,而L3 Cache是所有核心共享使用的,与内存直接联系。
  3. 如何写出让CPU跑得更快的代码?
    1. 简言之:Cache命中率高的代码。
    2. 现代,CPU在读写数据的时候,都是在CPU Cache读写数据的。
      1. 不要考虑下面的写直达的直接写回内存,只是一个概念,现在已经启用了。
  4. 为保证Cache和内存数据一致,需要将Cache中的数据同步到内存中。
    1. 写直达写回

写直达

  1. 把数据同时写入内存和Cache中
    1. 这是保持一致性最简单的方式。
  2. 步骤:
    1. 如果数据在CPU Cache中,先写入Cache Block再写入内存;否则直接写入内存。
  3. 问题:每次写操作都要写回到内存,耗时巨大。

写回

  1. 发生写操作时,新的数据仅仅被写入Cache Block里,只有当修改过的Cache Block被替换时才需要写到内存中
  2. 优化:
    1. 减少了数据写回内存的频率。
  3. 流程:
    1. 检查数据是否在Cache中。在的话直接将数据写入Cache Block并将该块标记为脏。
    2. 否则定位数据所对应的Cache Block,并查看Cache Block里面的数据是否是脏的。如果是脏的就先将Cache Block中的数据写回到内存再从内存中读取需要的数据到Cache Block。
    3. 否则直接从内存读取当前要写入的数据到Cache Block。
    4. 最后将当前数据写入到Cache Block。
    5. 补充:如有不解,见图 小林coding-2.4 CPU 缓存一致性
  4. 总结:
    1. 只有在缓存不命中,且数据对应的Cache Block被标记为脏的情况下才会将数据写回到内存中
    2. 好处:
      1. 如果缓存命中率高,可以极大幅度提升读写性能,因为不用每次都写回到内存。

缓存一致性问题与解决

  1. 多核心的CPU会带来缓存一致性的问题,因为L1/L2 Cache是多核心各自独有的。
  2. 缓存一致性问题
    1. 假设A,B号核心同时运行两个线程,都操作共同变量i(初始值为0)。
    2. 为了性能考虑,执行的是 写回策略。A号核心先执行i++,把值为1的结果写入到L1/L2 Cache并标记为脏,但是没有写回内存。
    3. 然后此时B核心尝试从内存读取i变量的值,则会读到i=0。
    4. A号核心和B号核心在这个过程中出现了缓存不一致的问题,会导致直接结果的错误——这就是缓存一致性问题。
  3. 解决缓存一致性必须要保证两点
    1. 写传播:某个核心的Cache数据更新时,必须要传播到其他核心的Cache。
    2. 事务的串行化:某个核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的。
      1. 比如,A核心的操作a,B核心的操作b,在C和D核心中的顺序不能一个是a->b,一个是b->a。
  4. 实现事务串行化需要做到2点:
    1. CPU核心对应Cache中数据的操作,需要同步给其他CPU核心。
      1. 这难道不就是写传播?但是这里相对于写传播有同步的概念?
    2. 要引入 锁 的概念,如果两个CPU核心里有相同数据的Cache,那么对于这个Cache数据的更新,只有拿到了锁,才能进行对于的数据更新。

总线嗅探——写传播的实现方式

  1. 介绍:
    1. 当一个CPU核心修改了L1 Cache中的数据,会通过总线把这个事件广播通知给其他所有的核心。
    2. 然后每个CPU核心都会监听总线上的广播事件。
      1. 并检查是否有相同的数据在自己的L1 Cache里面,如果有则更新。
  2. 总结:
    1. 广播+监听。
  3. 问题:
    1. 总线嗅探只能保证写传播,不能保证事务的串行化。
    2. CPU需要每时每刻监听总线上的一切活动,且不管别的核心是否缓存相同的数据都会发出一个广播事件,这无疑会加重总线的负载。
  4. 解决以上问题:
    1. MESI协议——基于总线嗅探实现了事务串行化,同时用状态机机制降低了总线负载。这个协议做到了CPU缓存一致性。

MESI——基于总线嗅探,真正实现了缓存一致性

  1. 介绍:
    1. Modified,已修改。
    2. Exclusive,独占。
    3. Shared,共享。
    4. Invalidated,已失效。
  2. 4个状态用于标记Cache Line。
    1. 已修改即脏标记,已经修改但是没有写到内存里。
    2. 已失效,表示Cache Block的数据已经失效。
    3. 独占和共享都代表Cache Block里的数据是干净的,也就是说数据和内存中是一致的。
      1. 区别在于,独占表示数据只存储在一个CPU核心的Cache里,写数据时不需要通知其他CPU核心;
      2. 共享表示相同的数据在多个CPU核心的Cache里都有,所以在修改前先要想其他CPU核心广播一个请求,把他们的Cache Line标记为无效状态,然后再更新当前Cache里的数据。
  3. 状态流转表格:详见小林coding-2.4 CPU 缓存一致性
    1. 这可不是一个很简单的东西,建议记住一些易错的东西(以下总结),然后原则上还是按它们的定义来推论。
  4. 表格的几点总结(不成系统):
    1. 一个数据再多核心中一定只有一个已修改M或者独占E。
      1. 已修改M和共享E区别:已修改M的Cache Line中的数据和内存中是不一样的。
    2. 只有遇到已修改M,才可能会将Cache Line中的数据写回内存。
      1. E,S,I都不会,具体自己体会以下秒懂。
    3. 只有遇到已失效I,才可能会从内存中读取数据到Cache Line。
      1. E,S,M都不会。
    4. 遇到已失效I:(容易搞错)
      1. 在本地核心 的时候,会先判断其他核心中是否有这份数据,同时判断其Cache Line状态,如果是已修改M则需要将其写入内存,本地核心再从内存中去,然后两个核心的Cache Line都变成S。
      2. 如果有,且Cache Line状态为S或者E,本地核心的Cache从内存中取数据,取完之后都标记为S。
        1. 注意不是从其他核心取,其他核心的数据只有在修改的时候会通过总线嗅探进行写传播。
      3. 其他核心没有这份数据自然从内存中读取并标记为E。
      4. 在本地核心 的时候,主要流程是从内存中取数据,缓存到Cache中,将其标记为M。
      5. 另种特殊情况:
        1. 如果其他核心的Cache有这份数据,且状态为M,则在从内存中取数据之前需要将其他核心里面的数据写入内存。
        2. 如果其他核心的Cache中有这份数据,则在本地Cache中更新数据前需要标记为I。
    5. 遇到已修改M:
      1. 写完之后会变成E或者S。
  5. 总结:
    1. E,S都表示有效且一致;
    2. M表示有效且不一致;
    3. I表示无效。
    4. 只要现在理解了,后面理解起来是非常简单的。。。

拓展1:伪共享问题相关

前言

  1. 参考:
    1. 小林coding-分析伪共享的问题

伪共享的问题

  1. 介绍:
    1. 如果两个核心持续交替的修改两个变量A、B,且两个变量归属于同一个Cache Line,那么就会导致CPU Cache失效,即每一次操作都会从内存中读取,这种现象称为伪共享。
  2. 失效的具体细节:
    1. 将两个变量同时读入到两个核心的同一个Cache Line中,标记为共享。
    2. 在修改的时候会不断的将另一个核心中的Cache Line置为已失效状态I,将正在操作的Cache Line置为已修改状态M。
    3. 所以在修改的时候会不断将另一个Cache Line写入到内存再从内存中读取数据到正在操作的核心中的Cache Line。
  3. 总结:
    1. 不同数据处于同一块,修改的时候不断将对方置为I,将自己置为M。导致不断的写回到内存和从内存中读取,CPU Cache失效。
  4. 补充1:
    1. 很多机器的L1 Cache Line大小为64字节,即可一次性载入的数据大小为64字节,相当于16个int型数据。

避免伪共享

  1. 简介:字节填充。
    1. 一般操作系统会有这样的命令。
  2. 具体实现:
    1. 将”热点“数据强制分开,通过填充字节。
    2. 结果是一个L1 Cache Line保证只有一个数据,这样会浪费一定空间,但是可以有效避免伪共享问题。

总结

  1. 缓存一致性问题:
    1. 多核心的Cache中数据和内存中数据不一致,各个核心的Cache中数据也不一致,就会造成缓存不一致,出现错误。
  2. 解决:
    1. 写传播和事务的串行化。
    2. 基于总线嗅探的MESI协议可以完美解决这个问题。
  3. 但是MESI协议有时候会引发伪共享的问题,我们可以通过填充字节来特殊解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值