与程序员相关的CPU缓存知识( 二 )

  • 为了避免上述的两种方案的问题,于是就要容忍一定的hash冲突,也就出现了 N-Way 关联 。也就是把连续的N个Cache Line绑成一组,然后,先把找到相关的组,然后再在这个组内找到相关的Cache Line 。

  • 与程序员相关的CPU缓存知识

    文章插图
     
    那么,Cache的命中率会成为程序运行性能非常关键的事,所以,了解上面的这些东西,会有利于我们知道在什么情况下有可以导致缓存的失效 。
    对于 N-Way 关联我们取个例子,并多说一些细节(因为后面会用到),Intel 大多数处理器的L1 Cache都是32KB,8-Way 组相联,Cache Line 是64 Bytes 。于是,
    • 32KB的可以分成,32KB / 64 = 512 条 Cache Line 。
    • 因为有8 Way,于是会每一Way 有 512 / 8 = 64 条 Cache Line 。
    • 于是每一路就有 64 x 64 = 4096 Byts 的内存 。
    为了方便索引内存地址,
    • Tag :每条 Cache Line 前都会有一个独立分配的 24 bits来存的 tag,其就是内存地址的前24bits
    • Index :内存地址后续的6个bits则是在这一Way的是Cache Line 索引,2^6 = 64 刚好可以索引64条Cache Line
    • Offset :再往后的6bits用于表示在Cache Line 里的偏移量
    如下图所示:(更多的细节可以读一下《 Cache: a place for concealment and safekeeping 》)
    与程序员相关的CPU缓存知识

    文章插图
     
    (图片来自《 Cache: a place for concealment and safekeeping 》)
    这意味着:
    • L1 Cache 可映射 36bits 的内存地址,一共 2^36 = 64GB的内存
    • 因为只要头24bits相同就会被映射到同一个Way中,所以,每4096个地址会放在一Way中 。
    • 当CPU要访问一个内存的时候,通过这个内存的前24bits 和中间的6bits可以直接定位相应的Cache Line 。
    此外,当有数据没有命中缓存的时候,CPU就会以最小为Cache Line的单元向内存更新数据 。当然,CPU并不一定只是更新64Bytes,因为访问主存是在是太慢了,所以,一般都会多更新一些 。好的CPU会有一些预测的技术,如果找到一种pattern的话,就会预先加载更多的内存,包括指令也可以预加载 。这叫 Prefetching 技术 (参看,Wikipedia 的 Cache Prefetching 和 纽约州立大学的 Memory Prefetching ) 。比如,你在for-loop访问一个连续的数组,你的步长是一个固定的数,内存就可以做到prefetching 。(注:指令也是以预加载的方式执行,参看本站的《 代码执行的效率 》中的第三个示例)
    缓存的一致性对于主流的CPU来说,缓存的写操作基本上是两种策略(参看本站《 缓存更新的套路 》),
    • 一种是Write Back,写操作只要在cache上,然后再flush到内存上 。
    • 一种是Write Through,写操作同时写到cache和内存上 。
    为了提高写的性能,一般来说,主流的CPU(如:Intel Core i7/i9)采用的是Write Back的策略,因为直接写内存实在是太慢了 。
    好了,现在问题来了,如果有一个数据 x 在 CPU 第0核的缓存上被更新了,那么其它CPU核上对于这个数据 x 的值也要被更新,这就是缓存一致性的问题 。(当然,对于我们上层的程序我们不用关心CPU多个核的缓存是怎么同步的,这对上层都是透明的)
    一般来说,在CPU硬件上,会有两种方法来解决这个问题 。
    • Directory 协议  。这种方法的典型实现是要设计一个集中式控制器,它是主存储器控制器的一部分 。其中有一个目录存储在主存储器中,其中包含有关各种本地缓存内容的全局状态信息 。当单个CPU Cache 发出读写请求时,这个集中式控制器会检查并发出必要的命令,以在主存和CPU Cache之间或在CPU Cache自身之间进行数据同步和传输 。
    • Snoopy 协议  。这种协议更像是一种数据通知的总线型的技术 。CPU Cache通过这个协议可以识别其它Cache上的数据状态 。如果有数据共享的话,可以通过广播机制将共享数据的状态通知给其它CPU Cache 。这个协议要求每个CPU Cache 都可以 “窥探” 数据事件的通知并做出相应的反应 。

    与程序员相关的CPU缓存知识

    文章插图
     
    因为Directory协议是一个中心式的,会有性能瓶颈,而且会增加整体设计的复杂度 。而Snoopy协议更像是微服务+消息通讯,所以,现在基本都是使用Snoopy的总线的设计 。
    这里,我想多写一些细节,因为这种微观的东西,不自然就就会更分布式系统相关联,在分布式系统中我们一般用Paxos/Raft这样的分布式一致性的算法 。而在CPU的微观世界里,则不必使用这样的算法,原因是因为CPU的多个核的硬件不必考虑网络会断会延迟的问题 。所以,CPU的多核心缓存间的同步的核心就是要管理好数据的状态就好了 。


    推荐阅读