0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

CPU缓存知识科普

sakobpqhz6 来源:IC学习 2023-07-17 12:26 次阅读

好久没有写一些微观方面的文章了,今天写一篇关于CPU Cache相关的文章,这篇文章比较长,主要分成这么几个部分:基础知识、缓存的命中、缓存的一致性和延伸阅读。其中会讲述一些多核 CPU 的系统架构以及其原理。这篇文章我会尽量地写简单和通俗易懂一些,主要是讲清楚相关的原理和问题,而对于一些细节和延伸阅读我会在文章最后会给出相关的资源。

因为无论你写什么样的代码都会交给CPU来执行,所以,如果你想写出性能比较高的代码,这篇文章中提到的技术还是值得认真学习的。另外,千万别觉得这些东西没用,这些东西非常有用,十多年前就是这些知识在性能调优上帮了我的很多大忙,从而跟很多人拉开了差距……

基础知识

首先,我们都知道现在的CPU多核技术,都会有几级缓存,老的CPU会有两级缓存(L1和L2),新的CPU会有三级缓存(L1,L2,L3 ),如下图所示:

9e82b700-2459-11ee-962d-dac502259ad0.png

其中:

L1缓存分成两种,一种是指令缓存,一种是数据缓存。L2缓存和L3缓存不分指令和数据。

L1缓存和L2缓存在每一个CPU核中,L3则是所有CPU核心共享的缓存。

L1、L2、L3缓存离CPU越近就越小,速度也越快,离CPU越远,速度就越慢。

再往后面就是内存,内存的后面就是硬盘。我们来看一些他们的速度:

L1 的存取速度:4 个CPU时钟周期

L2 的存取速度:11 个CPU时钟周期

L3 的存取速度:39 个CPU时钟周期

RAM内存的存取速度:107 个CPU时钟周期

我们可以看到,L1的速度是RAM的27倍,但是L1/L2的大小基本上也就是KB级别的,L3会是MB级别的。例如:Intel Core i7-8700K ,是一个6核的CPU,每核上的L1是64KB(数据和指令各32KB),L2 是 256K,L3有2MB(我的苹果电脑是 Intel Core i9-8950HK,和Core i7-8700K的Cache大小一样)。

我们的数据就从内存向上,先到L3,再到L2,再到L1,最后到寄存器进行CPU计算。为什么会设计成三层?这里有下面几个方面的考虑:

一个方面是物理速度,如果要更大的容量就需要更多的晶体管,除了芯片的体积会变大,更重要的是大量的晶体管会导致速度下降,因为访问速度和要访问的晶体管所在的位置成反比,也就是当信号路径变长时,通信速度会变慢。这部分是物理问题。

另外一个问题是,多核技术中,数据的状态需要在多个CPU中进行同步,并且,我们可以看到,cache和RAM的速度差距太大,所以,多级不同尺寸的缓存有利于提高整体的性能。

这个世界永远是平衡的,一面变得有多光鲜,另一面也会变得有多黑暗。建立这么多级的缓存,一定就会引入其它的问题,这里有两个比较重要的问题,

一个是比较简单的缓存的命中率的问题。

另一个是比较复杂的缓存更新的一致性问题。

尤其是第二个问题,在多核技术下,这就很像分布式的系统了,要对多个地方进行更新。

缓存的命中

在说明这两个问题之前。我们需要了解一个术语 Cache Line。缓存基本上来说就是把后面的数据加载到离自己近的地方,对于CPU来说,它是不会一个字节一个字节的加载的,因为这非常没有效率,一般来说都是要一块一块的加载的,对于这样的一块一块的数据单位,术语叫“Cache Line”,一般来说,一个主流的CPU的Cache Line 是 64 Bytes(也有的CPU用32Bytes和128Bytes),64Bytes也就是16个32位的整型,这就是CPU从内存中捞数据上来的最小数据单位。

比如:Cache Line是最小单位(64Bytes),所以先把Cache分布多个Cache Line,比如:L1有32KB,那么,32KB/64B = 512 个 Cache Line。

一方面,缓存需要把内存里的数据放进来,英文叫 Cache Associativity。Cache的数据放置策略决定了内存中的数据块会拷贝到CPU Cache中的哪个位置上,因为Cache的大小远远小于内存,所以,需要有一种地址关联的算法,能够让内存中的数据可以被映射到Cache中来。这个有点像内存地址从逻辑地址向物理地址映射的方法,但不完全一样。

基本上来说,我们会有如下的一些方法。

一种方法是,任何一个内存地址的数据可以被缓存在任何一个Cache Line里,这种方法是最灵活的,但是,如果我们要知道一个内存是否存在于Cache中,我们就需要进行O(n)复杂度的Cache遍历,这是很没有效率的。

另一种方法,为了降低缓存搜索算法,我们需要使用像Hash Table这样的数据结构,最简单的hash table就是做“求模运算”,比如:我们的L1 Cache有512个Cache Line,那么,公式(内存地址 mod 512)* 64就可以直接找到所在的Cache地址的偏移了。但是,这样的方式需要我们的程序对内存地址的访问要非常地平均,不然冲突就会非常严重。这成了一种非常理想的情况了。

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

9e9ae29e-2459-11ee-962d-dac502259ad0.png

对于 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》)

当拿到一个内存地址的时候,先拿出中间的 6bits 来,找到是哪组。

9ebd8f56-2459-11ee-962d-dac502259ad0.png

然后,在这一个8组的cache line中,再进行O(n) n=8 的遍历,主要是要匹配前24bits的tag。如果匹配中了,就算命中,如果没有匹配到,那就是cache miss,如果是读操作,就需要进向后面的缓存进行访问了。L2/L3同样是这样的算法。而淘汰算法有两种,一种是随机一种是LRU。现在一般都是以LRU的算法(通过增加一个访问计数器来实现)

9ece9e04-2459-11ee-962d-dac502259ad0.png

这也意味着:

L1 Cache 可映射 36bits 的内存地址,一共 2^36 = 64GB的内存

当CPU要访问一个内存的时候,通过这个内存中间的6bits 定位是哪个set,通过前 24bits 定位相应的Cache Line。

就像一个hash Table的数据结构一样,先是O(1)的索引,然后进入冲突搜索。

因为中间的 6bits 决定了一个同一个set,所以,对于一段连续的内存来说,每隔4096的内存会被放在同一个组内,导致缓存冲突。

此外,当有数据没有命中缓存的时候,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 都可以“窥探”数据事件的通知并做出相应的反应。如下图所示,有一个Snoopy Bus的总线。

9efa1070-2459-11ee-962d-dac502259ad0.png

因为Directory协议是一个中心式的,会有性能瓶颈,而且会增加整体设计的复杂度。而Snoopy协议更像是微服务+消息通讯,所以,现在基本都是使用Snoopy的总线的设计。

这里,我想多写一些细节,因为这种微观的东西,让人不自然地就会跟分布式系统关联起来,在分布式系统中我们一般用Paxos/Raft这样的分布式一致性的算法。而在CPU的微观世界里,则不必使用这样的算法,原因是因为CPU的多个核的硬件不必考虑网络会断、会延迟的问题。所以,CPU的多核心缓存间的同步的核心就是要管理好数据的状态就好了。

这里介绍几个状态协议,先从最简单的开始,MESI协议,这个协议跟那个著名的足球运动员梅西没什么关系,其主要表示缓存数据有四个状态:Modified(已修改), Exclusive(独占的),Shared(共享的),Invalid(无效的)。

这些状态的状态机如下所示(有点复杂,你可以先不看,这个图就是想告诉你状态控制有多复杂):

9f0f7fe6-2459-11ee-962d-dac502259ad0.png

下面是个示例(如果你想看一下动画演示的话,这里有一个网页(MESI Interactive Animations),你可以进行交互操作,这个动画演示中使用的Write Through算法):

9f2adaca-2459-11ee-962d-dac502259ad0.png

MESI 这种协议在数据更新后,会标记其它共享的CPU缓存的数据拷贝为Invalid状态,然后当其它CPU再次read的时候,就会出现 cache miss 的问题,此时再从内存中更新数据。从内存中更新数据意味着20倍速度的降低。我们能不能直接从我隔壁的CPU缓存中更新?是的,这就可以增加很多速度了,但是状态控制也就变麻烦了。还需要多来一个状态:Owner(宿主),用于标记,我是更新数据的源。于是,出现了 MOESI 协议

MOESI协议的状态机和演示示例我就不贴了(有兴趣可以上Berkeley上看看相关的课件),我们只需要理解MOESI协议允许 CPU Cache 间同步数据,于是也降低了对内存的操作,性能是非常大的提升,但是控制逻辑也非常复杂。

顺便说一下,与 MOESI 协议类似的一个协议是MESIF,其中的 F 是 Forward,同样是把更新过的数据转发给别的 CPU Cache 但是,MOESI 中的 Owner 状态 和MESIF 中的 Forward 状态有一个非常大的不一样——Owner状态下的数据是dirty的,还没有写回内存,Forward状态下的数据是clean的,可以丢弃而不用另行通知。

需要说明的是,AMD用MOESI,Intel用MESIF。所以,F 状态主要是针对 CPU L3 Cache 设计的(前面我们说过,L3是所有CPU核心共享的)。(相关的比较可以参看StackOverlow上这个问题的答案)

总之,这个CPU Cache的调优技术不是什么新鲜的东西,只要Google就能找到有很多很多文章……

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 芯片
    +关注

    关注

    447

    文章

    47788

    浏览量

    409128
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10442

    浏览量

    206560
  • 缓存
    +关注

    关注

    1

    文章

    220

    浏览量

    26441
  • 多核技术
    +关注

    关注

    0

    文章

    5

    浏览量

    7146

原文标题:CPU缓存知识

文章出处:【微信号:IC学习,微信公众号:IC学习】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    CPU一级缓存与二级缓存深度分析

    CPU缓存:通过优化的的读取机制,可以使CPU读取缓存的命中率非常高,也就是说CPU下一次要读取的数据90%都在
    的头像 发表于 04-16 10:52 1.9w次阅读
    <b class='flag-5'>CPU</b>一级<b class='flag-5'>缓存</b>与二级<b class='flag-5'>缓存</b>深度分析

    CPU、GPU和内存知识科普

    本文内容包括CPU、内存和GPU知识,本期重点更新GPU和CPU部分知识。比如:GPU更新包括架构演进,最新产品A100、选型策略、架构分析、散热和规格分类等。
    的头像 发表于 11-13 11:47 887次阅读
    <b class='flag-5'>CPU</b>、GPU和内存<b class='flag-5'>知识</b><b class='flag-5'>科普</b>

    CPU一级缓存

    CPU一级缓存              CPU缓存(Cache
    发表于 12-24 10:21 435次阅读

    CPU二级缓存容量

    CPU二级缓存容量            CPU缓存(Cache Memory)是位
    发表于 12-24 10:22 485次阅读

    什么是CPU一级缓存/二级缓存

    什么是CPU一级缓存/二级缓存?  即L1 Cache。集成在CPU内部中,用于CPU在处理数据过程中数据的暂时保存。由于
    发表于 02-04 10:43 1025次阅读

    什么是CPU缓存/前端总线(FSB)频率

    什么是CPU缓存/前端总线(FSB)频率  缓存   缓存大小也是CPU的重要指标之一,而且
    发表于 02-04 11:22 1294次阅读

    CPU缓存对性能的影响

      说到CPU,不得不说的就是CPU缓存,目前CPU缓存已经成了衡量CPU性能的一个必要指标,
    发表于 11-13 17:58 2164次阅读

    CPU缓存是什么意思_CPU缓存有什么作用

    由于处理器是核心硬件,相信我们在选择处理器的时候都会去关心处理器参数方面,而在处理器核心参数中,我们经常会看到缓存(Cache)这个参数,那么CPU缓存有什么作用呢?下面小编科普一下
    发表于 05-19 09:24 6513次阅读

    服务器的CPU是什么呢,服务器CPU科普知识

    对于cpu来说,是要看你打算一天开几个区的然后去选择。(根据开区情况,选择合适的) 服务器CPU科普知识: 1.服务器CPU的指令一般是采用的RISC(精简指令集)。这种设计的好处就
    发表于 11-12 14:14 5079次阅读

    缓存如何工作,如何设计CPU缓存

    20世纪80年代,CPU性能有了显著提升,但这受到板载内存访问速度缓慢增长的阻碍。随着这种差异的恶化,工程师们发现了一种通过新的设计技术缓存来解决问题的方法。本文将帮助你进一步了解什么是缓存,它如何工作以及如何设计
    的头像 发表于 11-19 17:23 2442次阅读

    CPU缓存的作用及原理有哪些

    CPU缓存是位于CPU与内存之间的临时存储器,它的容量比内存小很多,但交换速度比内存要快很多。 CPU缓存分为三类:一级
    的头像 发表于 08-27 15:58 9588次阅读

    关于CPU缓存的作用

    CPU没有存储功能,那么缓存到底是干什么的?CPU缓存是用于减少处理器访问内存所需平均时间的部件,作用类似于CPU内部的内存。
    的头像 发表于 03-30 10:58 3962次阅读
    关于<b class='flag-5'>CPU</b><b class='flag-5'>缓存</b>的作用

    cpu缓存的作用及原理是什么

    CPU高速缓存集成于CPU的内部,其是CPU可以高效运行的成分之一,本文围绕下面三个话题来讲解CPU缓存
    发表于 08-21 12:17 887次阅读
    <b class='flag-5'>cpu</b><b class='flag-5'>缓存</b>的作用及原理是什么

    电气基本知识科普

    电气基本知识科普
    的头像 发表于 09-09 10:23 4598次阅读
    电气基本<b class='flag-5'>知识</b><b class='flag-5'>科普</b>

    CPU缓存那些事儿

    CPU Cache 在读取内存数据时,每次不会只读一个字或一个字节,而是一块块地读取,这每一小块数据也叫CPU 缓存行(CPU Cache Line)。这也是对局部性原理的运用,当一个
    的头像 发表于 09-10 10:57 387次阅读
    <b class='flag-5'>CPU</b><b class='flag-5'>缓存</b>那些事儿