美文网首页
JAVA线程-CPU缓存和内存屏障(四)

JAVA线程-CPU缓存和内存屏障(四)

作者: IT人故事会 | 来源:发表于2020-08-06 09:21 被阅读0次

    上节说了线程中止,优雅和暴力的方式,也说到了通过标志位的方式,这次一起说说CPU缓存和内存屏障。

    (一)CPU性能优化手段

    • ① 缓存

    为了提高程序运行的性能,CPU已经i7 10代了,很多方面对程序进行优化。
    CPU 告诉缓存,硬盘很慢,运用缓存加载到内存里面,提高的访问速度,尽可能地避免处理器访问主内存的时间开销,处理器大多会一用缓存(cache)以提高性能。这也是目前大部分的处理器使用的机制,处理器的缓存。

    • ② 多级缓存

    L1 Cache (一级缓存)是CPU第一层高速缓存, 分为数据缓存和底层的指令缓存, 一般服务器CPU的L1缓存的容量通常在32-4096kb。

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

    L1和L2前面的容量都是有限的,就提出了L3,L3 Cache(三级缓存)现在都是内置的, 而它的实际作用既是, L3缓存的应用可以进一步降低内存延迟, 同时提升大数据量计算时处理器的性能. 具有较大L3缓存的处理器更有效的文件系统缓存行为及较短消息和处理器队列长度. 一般是多核共享一个L3缓存。不管你电脑有多少个CPU,每个CPU都有L1 和 L2,但是L3都是共用的。

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

    上边主要是了解一些概念就可以了。

    • ③ 缓存同步协议

    多CPU读取同样的数据进行缓存。多个CPU读取同样的数据,修改同样的数据,首先数据体验在缓存上面,最终写入主内存以哪个CPU为准?如果有4个CPU,都对一个 i 进行修改,都体现在自己的L1 一级缓存里面,我们需要将缓存数据写入到内存里面,在这种高速缓存回写的场景下,有一个缓存一致性协议多数CPU厂家对它进行了实现,也就是多个CPU厂家一起定义了一个协议。

    每个缓存都必须有个状态位,目前定义了4个状态位。

    1.修改态(Modified),此cache行已被修改过(脏行),内容已不同于主存,为此cache专有。
    2.专有态(Exclusive),此cache行内容同于主存,但不出现于其他cache中。
    3.共享态(Shared),此cache行内容同于主存,但也出现于其他cache中。
    4.无效态(Invalid),此cache行内容无效,需要从主内存重新加载。

    MESI协议:多处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU,也就是意味着,CPU处理要控制自己的读写操作,还要监听其中他CPU发出来的通知,从而保证最终一致。不仅自己干活,还要看看别人干了什么事情。

    • ④ 运行时指令重排

    指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。可能将后面的读缓存命令有限执行。

    //代码
    int x  = 10;
    int y = z;
    

    正常执行的三步骤
    1.将10写入x
    2.读取z的值
    3.将z值写入y

    指令重排后执行
    1.读取z的值
    2.将z值写入y
    3.将10写入x

    指令重排并非随意的,需要最受as-if-serial语义
    不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。不会对存在数据依赖关系的操作做重排序。
    分析: 关键词是单线程情况下,必须遵守;其余的不遵守。

    • ⑤ CPU高速缓存和CPU执行指令重排序的问题

    1.缓存中的数据与主内存的数据并不是实时同步的, 各CPU间缓存的数据也不是实时同步. 在同一时间点, 各CPU所看到的同一内存地址的数据的值可能是不一致的。
    2.虽然遵守了as-if-serial语义, 但仅在单CPU自己执行的情况下能保证结果正确. 多核多线程中, 指令逻辑无法分辨因果关联, 可能出现乱序执行, 导致程序运行结果错误。

    • ⑥ 解决CPU告诉缓存和CPU质量重排序的问题

    1.写内存屏障(Store Memory Barrier): 在指令后插入Store Barrier, 能让写入缓存中的最新数据更新写入主内存, 让其他线程可见强制写入主内存, 这种显示调用, CPU就不会因为性能考虑而进行指令重排。

    2.读内存屏障(Load Memory Barrier): 在指令前插入Load Barrier, 可以让高速缓存中的数据失效, 强制从新从主内存读取数据强制读取主内存内容, 让CPU缓存和主内存保持一致, 避免了缓存导致的一致性问题。

    PS:本节主要是为后面的JVM线程安全问题做个铺垫。同时也看到了现代CPU不断的严禁,在程序运行优化中做出的努力。不同CPU厂家付出的人力物力成本,最终体现在不同的CPU性能差距上。同样是做cpu,有的可以跑lol手游,有的只能玩贪吃蛇,这就是性能的区别。

    相关文章

      网友评论

          本文标题:JAVA线程-CPU缓存和内存屏障(四)

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