缓存(cache)登场
接上篇内容,我们就制作一块速度极快但是容量极小的存储设备。那么其成本也不会太高。这块存储设备我们称之为cache memory。在硬件上,我们将cache放置在CPU和主存之间,作为主存数据的缓存。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。当存在cache的时候,以上程序如何运行的例子的流程将会变成如下:
CPU和主存之间直接数据传输的方式转变成CPU和cache之间直接数据传输。cache负责和主存之间数据传输。
缓存如何工作的
我们来看上面讲的两个程序。
如果没有缓存,则CPU需要将数组的元素一个接着一个的读到CPU中,然后进行赋值,先花65ns读取a[0][0]进行赋值,然后再花65ns写回到主存,接着进行后面的元素操作。
此时,有了缓存。Cache先将这些数据按照块的大小(这里假设64 Byte为一个块)从主存中读到cache里面。然后CPU就可以花几个ns就可以从Cache中拿到a[0][0]/a[0][1]……的数据,是不是快了很多?
那为什么程序2会比程序1慢呢?程序2也有cache呀?
这就要说道cache的工作原理了。简单的说,Cache从主存中获取数据是按照块儿来进行的,一般一次搬运或者更新64Byte数据,成为一个cache line。而cache line在主存中是连续存在的,比如a[0][0]~a[0][63]组成了一个cache line,被cache一次性写入到cache中。这样,CPU就可以不间断的从cache中连续获取a[0][0]~a[0][63]了。
但程序2的写法是,CPU获取完a[0][0]后,读的数据是a[1][0],明显不在上述的cache line中,这时候cache还需要从新从主存中获取一个cache line,其中包含了a[1][0]~a[1][63]。
依次类推,每一次CPU想读下一个数据的时候,都需要cache重新从主存中获取数据,这也就没有体现出了cache line的作用了,时间都花费在了cache从主存中获取cache line上了。
这样就引入了另一个概念,程序局部性原理:
程序局部性就是读写内存数据时读写连续的内存空间,目的是让缓存可以命中,减少缓存缺失导致替换的开销。
缓存正式使用了程序局部性原理来实现数据获取的速度提升。而程序2正好破坏了程序局部性,导致缓存的作用大大降低。
缓存的结构
cache的速度在一定程度上同样影响着系统的性能。一般情况cache的速度可以达到1ns,几乎可以和CPU寄存器速度媲美。但是,这就满足人们对性能的追求了吗?并没有。
当cache中没有缓存我们想要的数据的时候,依然需要漫长的等待从主存中load数据。为了进一步提升性能,引入多级cache。前面提到的cache,称之为L1 cache(第一级cache)。我们在L1 cache 后面连接L2 cache,在L2 cache 和主存之间连接L3 cache。等级越高,速度越慢,容量越大。但是速度相比较主存而言,依然很快。
经过3级cache的缓冲,各级cache和主存之间的速度最萌差也逐级减小。在一个真实的系统上,各级cache之间硬件上是如何关联的呢?我们看下Cortex-A53架构上各级cache之间的硬件抽象框图如下:
在Cortex-A53架构上,L1 cache分为单独的instruction cache(ICache)和data cache(DCache)。L1 cache是CPU私有的,每个CPU都有一个L1 cache。一个cluster 内的所有CPU共享一个L2 cache,L2 cache不区分指令和数据,都可以缓存。所有cluster之间共享L3 cache。L3 cache通过总线和主存相连。
多级cache之间的配合工作
首先引入两个名词概念,命中和缺失。 CPU要访问的数据在cache中有缓存,称为“命中” (hit),反之则称为“缺失” (miss)。多级cache之间是如何配合工作的呢?我们假设现在考虑的系统只有两级cache。
当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,如果命中则把数据返回给CPU。如果L1 cache缺失,则继续从L2 cache中查找。
当L2 cache命中时,数据会返回给L1 cache以及CPU。如果L2 cache也缺失,很不幸,我们需要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。
这种多级cache的工作方式称之为inclusive cache。某一地址的数据可能存在多级缓存中。与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。
那为什么L1cache 不能做的很大呢?L2 cache 最大能做到多少? 如何确定L3 cache 的大小呢?
这里简单介绍下,感兴趣的可以自行搜索。
首先,目前的主流CPU中(Intel AMD ARM)L1 cache大小一般比较固定,64KB I cache + 64 KB D cache,原因可是以目前的技术,做大的话难易保持低延迟的访问。
L2 cache 目前主流CPU中一般采用了512KB~2MB大小的容量。
L3 cache则与CPU的架构有关,一般的原则是每个线程分配1MB的L3 cache容量。