美文网首页并发编程语言爱好者Java
java并发编程(十五)CPU缓存结构是啥样的?

java并发编程(十五)CPU缓存结构是啥样的?

作者: 我犟不过你 | 来源:发表于2021-12-30 10:46 被阅读0次

    一、CPU缓存结构

    现代CPU通常都是由三层缓存架构组成的,如下图所示:

    CPU缓存结构.png

    windows下的cpu:

    windows

    查看linux的cpu缓存如下:

    [root@public-server9 ~]# lscpu
    Architecture:          x86_64
    CPU op-mode(s):        32-bit, 64-bit
    Byte Order:            Little Endian
    CPU(s):                4
    On-line CPU(s) list:   0-3
    Thread(s) per core:    1
    Core(s) per socket:    4
    座:                 1
    NUMA 节点:         1
    厂商 ID:           GenuineIntel
    CPU 系列:          6
    型号:              79
    型号名称:        Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
    步进:              1
    CPU MHz:             2399.998
    BogoMIPS:            4799.99
    超管理器厂商:  VMware
    虚拟化类型:     完全
    L1d 缓存:          32K
    L1i 缓存:          32K
    L2 缓存:           256K
    L3 缓存:           35840K
    
    • 各缓存之间的效率如下所示:

      假设执行一条指令

      名称 大约需要的时钟周期(cycle)
      寄存器 1
      L1 3~4
      L2 10~20
      L3 40~45
      内存 120~240
    • 为什么如此设计?

      缓存的意义在于缓存热点数据,随着科技进步以及热点数据的逐步增加,一级缓存已经不能满足了。

      二级缓存是一级缓存的缓冲,一级缓存速度快,造价高,容量小。

      三级缓存则作为二级缓存的缓冲,速度相对于二级缓存还要更慢。不同之处在于,三级缓存时多个核心共享的一个缓冲。可以认为 是一个更小但是更快的内存。

    二、缓存行(Cache LIne)

    相信大家应该都听过缓存行,作为CPU缓存中的最小缓存单元,通常是大小是64字节。

    我们可以使用如下的方式查看linux下的缓存行大小:

    [root@public-server9 ~]#  cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
    64
    

    CPU当中数据的移动不是以一个字节为单位,而是以一个缓存行为单位的。

    当 CPU 把内存的数据载入缓存时,会把临近的64Byte的数据一同放入同一个Cache line中。

    根据空间局部性原理:临近的数据在将来被访问的可能性大。

    三、CPU缓存一致性

    多个cpu对同一块内存同时读写,会引起冲突问题,这一问题就是CPU的缓存一致性问题。

    如何保证多级缓存中的数据一致性呢?

    MESI协议 是基于Invalidate的高速缓存一致性协议,并且是支持回写高速缓存的最常用协议之一。MESI协议要求在缓存不命中且数据块在另一个缓存时,允许缓存到缓存的数据复制。这样一来减少了主存的事务操作,极大提高了性能。

    MESI中每个缓存行都有四个状态,分别是:

    • E(exclusive)独占

      缓存行只在当前缓存中,但是是干净的(clean)-- 缓存数据与主存数据相同。
      当别的缓存读取它时,状态变为共享(S);
      当前写数据时,变为已修改状态(M)。

    • M(modified)已修改

      缓存行是脏的(dirty),与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享(S)。

    • S(shared)共享

      缓存行也存在于其它缓存中且是干净的。

    • I(invalid)无效

      缓存行是无效的

    下面简要描述一下状态转换的流程和关系:

    1)M、E、S状态下的缓存行,都可以满足CPU的读请求;I状态是无效的,会重新去获取。

    2)E状态下的缓存行,当发生写请求时,会将状态转换成M,但此时并不向主存同步。E状态下的缓存行要监听读请求,当有读请求时,需要将状态变为S。

    3)M状态下的缓存行,需要监听其读操作,如果发生读操作,会将其他缓存当中的该缓存行(S状态),变成I状态,并且将自己写入主存,然后变成S状态。

    4)S状态的缓存行,如果发生写请求,会将自己变成M状态,如果有读请求,则会重复3)中的步骤,将其他缓存中的缓存行变成I状态,将自己写入主存,变成S状态。

    5)S状态下的缓存行,需要监听该缓存行的失效操作,如果发生失效操作,需要将自身变成I状态。

    6)I状态的缓存行发生读请求,需要从主存获取。

    上面就形成了一个闭环。

    本文主要对CPU的缓存一致性做一个入门了解,便于多线程并发编程的学习,过多的本文将不讲解了。

    四、内存屏障

    下面简单了解一下什么是内存屏障。

    前面的文章当中,我们曾提到过线程间的可见性,以及有序性,那么是如何实现的呢?当时我们说是使用volatile关键字,其底层的本质就是通过内存屏障。

    内存屏障主要分为读屏障写屏障

    • 可见性

      • 写屏障:对于共享变量的修改,在写屏障之前,都要同步到主存当中。
      • 读屏障:对于共享变量的修改,在读屏障之后,都要加载主存中的数据。
    • 有序性

      • 写屏障:确保发生指令重排序时,不会将写屏障前的数据排在写屏障的后面。
      • 读屏障:确保发生指令重排序时,不会将读屏障之后的代码排在读屏障之前。

    本篇简单了解原理,下一章节我们会学习volatile的原理。

    相关文章

      网友评论

        本文标题:java并发编程(十五)CPU缓存结构是啥样的?

        本文链接:https://www.haomeiwen.com/subject/sbbhqrtx.html