[toc]数据库
缓存又叫高速缓存,是计算机存储器中的一种,本质上和硬盘是同样的,都是用来<font color=#0000FF size=3>存储数据和指令的 </font>。它们最大的区别在于<font color=#0000FF size=3>读取速度的不一样。</font>程序通常是放在内存中的,当CPU执行程序的时候,执行完一条指令须要从内存中读取下一条指令,读取内存中的指令要花费100000个时钟周期(缓存读取速度为200个时钟周期,相差500倍),若是每次都从内存中取指令,CPU运行时将花费大量的时间在读取指令上。这显然是一种资源浪费。编程
如何解决这个问题呢?有人确定会问,<font color=#0000FF size=3>直接把程序存储在缓存中不行吗? </font>数组
答案是能够的。可是,缓存的造价太贵了。具体以下图所示。以2015年的售价为例,1GB SRAM的价格大约为327680美圆,而1GB 普通硬盘的价格仅仅为0.03美圆。用缓存来存储程序成本过高了,得不偿失。浏览器
因而,有人就提出了这样一种方法,<font color=#0000FF size=3>在CPU和内存之间添加一个高速内存, </font>这个高速内存容量小,只用来存储CPU执行时经常使用的指令。既保证了硬件成本,又提升了CPU的访问速度。这个高速内存就是缓存(高速缓存)。缓存
高速缓存是一个小而快速的<font color=#0000FF size=3>存储设备 </font>,它做为存储在更大更慢的设 备中的数据对象的缓冲区域。<font color=#0000FF size=3>使用高速缓存的过程称为缓存 </font>。服务器
具体以下图所示,主存能够做为一个存储设备,L3是主存的缓冲区域,从L3存取数据的过程就叫作缓存。网络
以下图所示,数据老是以<font color=#0000FF size=3>块为单位 </font>在高速缓存和主存之间来回复制。性能
若是咱们的程序请求一个数据字,这个数据字存储在编号为10的块中。将分如下几种状况考虑:spa
1. 高速缓存行中为空,这叫作<font color=#0000FF size=3>冷不命中 </font>。翻译
2.高速缓存中有数据块,但没有数据块10,这叫作<font color=#0000FF size=3>缓存不命中 </font>。接下来缓存请求主存将该块复制到高速缓存,高速缓存接收到以后将替换一个现有的数据块,从而存储新的数据块在高速缓存中。最后,高速缓存将数据块10返回给CPU。
3. 高速缓存中有数据,将内存中的数据块放置到高速缓存中时,发生了冲突,这叫作<font color=#0000FF size=3>冲突不命中 </font>。
放置策略中最经常使用的是:第k+1层的块i必须放在第k层的块(i mod 4)中。好比,第k+1层的0,4,8,12会映射到第k层的块0。块1,5,9,13会映射到块1。
4. 缓存中有数据块10,则直接返回给CPU。这叫作<font color=#0000FF size=3>缓存命中 </font>。
高速缓存彻底由硬件管理,硬件逻辑必需要知道,如何查找缓存中的块,并肯定是否包含特定块。所以,必须以很是严格且简单的方式去构建高速缓存。在计算机中,高速缓存模型以下图所示。
咱们能够将高速缓存存储器视为有$S = {2^s}$个高速缓存组的<font color=#0000FF size=3>数组 </font>。每一个组包含$E = {2^e}$个<font color=#0000FF size=3>高速缓存行 </font>。每一个行是由一个$B = {2^b}$字节的数据块组成的。
通常而言,高速缓存的结构能够用元组(S,E,B,m)来描述。高速缓存的大小(或容量)C指的是全部块的大小的和。<font color=#0000FF size=3>标记位和有效位不包括在内 </font>。所以,C=S×E×B。
每一个高速缓存存储器有m位,能够组成$M = {2^m}$个不一样的地址,$m = t + s + b$。每一个数据块由如下三部分构成。
有效位:有效位为t位,t通常为1,指明这个行是否包含有效信息。
标记位:标记位为s位。惟一的标识了存储在高速缓存中的块(数组索引)。
块偏移:数据块为$B = {2^b}$字节。指明CPU请求的内容在数据块中的偏移。
下面对以上内容出现的参数作个总结:
参数 | 描述 |
---|---|
$S = {2^s}$ | 组数 |
$E$ | 每一个组的行数 |
$B = {2^b}$ | 块大小(字节) |
$m = {\log _2}(M)$ | 物理地址位数 |
$M = {2^m}$ | 内存地址的最大数量 |
$s = {\log _2}(S)$ | 组索引位数量 |
$b = {\log _2}(B)$ | 块偏移位数量 |
$t = m - (s + b)$ | 标记位数量 |
$C = B \times E \times S$ | 不包括像有效位和标记位这样开销的高速缓存大小(字节) |
下表为现代计算机中用到的各类缓存。
类型 | 缓存什么 | 被缓存在何处 | 延迟(周期数) | 由谁管理 |
---|---|---|---|---|
CPU寄存器 | 4字节或8字节 | 芯片上的CPU寄存器 | 0 | 编译器 |
TLB | 地址翻译 | 芯片上的TLB | 0 | 硬件MMU |
L1高速缓存 | 64字节块 | 芯片上的L1高速缓存 | 4 | 硬件 |
L2高速缓存 | 64字节块 | 芯片上的L2高速缓存 | 10 | 硬件 |
L3高速缓存 | 64字节块 | 芯片上的L3高速缓存 | 50 | 硬件 |
虚拟内存 | 4KB页 | 主存 | 200 | 硬件 |
缓冲区缓存 | 部分文件 | 主存 | 200 | OS |
磁盘缓存 | 磁盘扇区 | 磁盘控制器 | 100000 | 控制器固件 |
网络缓存 | 部分文件 | 本地磁盘 | 10000000 | NFS客户 |
浏览器缓存 | Web页 | 本地磁盘 | 10000000 | Web浏览器 |
Web缓存 | Web页 | 远程服务器磁盘 | 1000000000 | Web代理服务器 |
当一条加载指令指示CPU从主存地址A中读取一个字w时,会将该主存地址A发送到高速缓存中,则高速缓存会根据如下步骤判断地址A是否命中:
组选择:根据地址划分,将中间的s位表示为无符号数做为<font color=#0000FF size=3>组的索引 </font>,可获得该地址对应的组。
行匹配:根据地址划分,可获得t位的标志位,因为组内的任意一行均可以包含任意映射到该组的数据块,因此就要线性搜索组中的每一行,<font color=#0000FF size=3>判断是否有和标志位匹配且设置了有效位的行 </font>,若是存在,则缓存命中,不然缓冲不命中。
字抽取:若是找到了对应的高速缓存行,则能够将b位表示为无符号数做为<font color=#0000FF size=3>块偏移量 </font>,获得对应位置的字。
当高速缓存命中时,会很快抽取出字w,并将其返回给CPU。若是缓存不命中,CPU会进行等待,高速缓存会向主存请求包含字w的数据块,当请求的块从主存到达时,高速缓存会将这个块保存到它的一个高速缓存行中,而后从被存储的块中抽取出字w,将其返回给CPU。
上面咱们介绍了计算机中的高速缓存模型,咱们能够根据每一个组的高速缓存行数E,将高速缓存分红不一样的类型。下面咱们看下直接映射高速缓存(E=1)的具体例子。
组选择示意图以下所示。假设有 S 组,每组由一行组成,缓存块为8字节。CPU发出地址要取数据字,高速缓存将该地址分解为三部分,对于图中的地址来讲,<font color=#FF4500 size=3>块偏移量为4。组索引是 1 ,粉红色的为t位标记位。 </font>所以,高速缓存提取的组索引为 1,即图中第二行。
而后,检查地址中的标记位与缓存行中的标记位是否匹配。若是匹配,将进行下一步字选择。若是不匹配,则表示未命中。在未命中时,<font color=#0000FF size=3>高速缓存必须从内存中从新取数据块, </font>在行中覆盖此块。
当标记位匹配时,表示命中,接着检查地址中的块偏移为4,即要从缓存行数据块的第5位开始取值,并返回给CPU。
下面,咱们模拟下直接映射高速缓存的过程,以便加深理解高速缓存是如何工做的。假设,内存地址为<font color=#FF4500 size=3>4字节,S=4组,E=1行/组,B=2字节/块。 </font>其结构图以下所示。
咱们模拟CPU要从高速缓存中读取地址为0,1,7,8,0的数据。下面是具体的过程。
地址 | 二进制 | 是否命中 |
---|---|---|
0 | [${0000_2}$](t=0,s=00,b=0) | |
1 | [${0001_2}$](t=0,s=00,b=1) | |
7 | [${0111_2}$](t=0,s=11,b=1) | |
8 | [${1000_2}$](t=1,s=00,b=0) | |
0 | [${0000_2}$](t=00,s=0,b=0) |
1. 读地址0的数据。<font color=#FF4500 size=3>标记位为0,索引位为00,偏移位为0,块号为0。</font>缓存行中没有数据,组0的有效位为0,地址的标记位和组0的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块0,块1, 共2字节,并存储在组0中。具体以下图所示。
2. 读地址1的数据。<font color=#FF4500 size=3>标记位为0,索引位为00,偏移位为1,块号1。 </font>缓存行中已有数据数据,组0的有效位为1,地址1的标记位和组0的标记位匹配,所以,命中。具体以下图所示。
3. 读地址7的数据。<font color=#FF4500 size=3>标记位为0,索引位为11(3),偏移位为1,块号为3。 </font>缓存行中有数据,组3的有效位为0,地址的标记位和组0的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块6,块7, 共2字节,并存储在组3中。具体以下图所示。
4. 读地址8的数据。<font color=#FF4500 size=3>标记位为1,索引位为00,偏移位为0,块号为4。 </font>缓存行中有数据,组0的有效位为1,地址的标记位和组0的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块8,块9, 共2字节,并存储在组0中。具体以下图所示。
5. 读地址0的数据。<font color=#FF4500 size=3>标记位为0,索引位为00,偏移位为0,块号为0。</font>缓存行中有数据,组0的有效位为1,地址的标记位和组0的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块0,块1, 共2字节,并存储在组0中。具体以下图所示。
最终结果以下:缓存命中率为20%。
地址 | 二进制 | 是否命中 |
---|---|---|
0 | [${0000_2}$](t=0,s=00,b=0) | 否 |
1 | [${0001_2}$](t=0,s=00,b=1) | 是 |
7 | [${0111_2}$](t=0,s=11,b=1) | 否 |
8 | [${1000_2}$](t=1,s=00,b=0) | 否 |
0 | [${0000_2}$](t=00,s=0,b=0) | 否 |
注意:块大小为2字节,因此从内存中取数据老是以偶数倍开始的,因此会看到M[8-9],而不是M[7-8]。
若是你看懂了上述高速缓存的整个过程,考虑下<font color=red size=3>如何编程来模拟高速缓存呢? </font>后面的文章我会详细讲解如何用C语言模拟高速缓存,欢迎关注个人公众号【嵌入式与Linux那些事】,第一时间获取更新。
观察以上过程其实能够发现,在第5步,读地址0的数据的时候,咱们又得<font color=#0000FF size=3>从新从内存中取数据到缓存行中。 </font>在读地址8的数据的时候,M[8-9]替换了缓存行中的M[0-1]。
<font color=#0000FF size=3>最主要的缘由是每个组中只容许存放一行缓存。 </font>假设,E = 2,每组中有2个缓存行,M[8-9]和M[0-1]就有很大可能同时存在于组0中。咱们在第5步访问时,就不须要从新从内存中取数据了。所以,就有了E = 2的两路相联高速缓存。
直接映射高速缓存中冲突不命中形成的问题源于每一个组只有一行这个限制。组相联高速存放松了这条限制,因此每一个组都保存有多于一个的高速缓存行。以下图所示为两路相联的高速缓存。
它的组选择与直接映射高速缓存的组选择同样,组索引位标识组。具体以下图所示,这里再也不赘述。
组相联高速缓存中的行匹配比直接映射高速缓存中的更复杂,由于它必须每次检查<font color=#0000FF size=3>多个行 </font>的标记位和有效位,以肯定所请求的字是否在集合中。具体以下图所示。
字选择的过程和直接映射高速缓存中的方式同样,这里就再也不赘述。
下面,咱们模拟下两路相联高速缓存的过程,以便加深理解高速缓存是如何工做的。假设,内存地址为4字节,S=2组,E=2行/组,B=2字节/块。其结构图以下所示。
咱们模拟CPU要从高速缓存中读取地址为0,1,7,8,0的数据。下面是具体的过程。
地址 | 二进制 | 是否命中 |
---|---|---|
0 | [${0000_2}$] (t=00,s=0,b=0) | |
1 | [${0001_2}$](t=00,s=0,b=1) | |
7 | [${0111_2}$](t=01,s=1,b=1) | |
8 | [${1000_2}$](t=10,s=0,b=0) | |
0 | [${0000_2}$](t=00,s=0,b=0) |
1. 读地址0的数据。<font color=#FF4500 size=3>标记位为00,索引位为0,偏移位为0,块号为0。</font>缓存行中没有数据,组0的有效位为0,地址的标记位和组0的第一行和第二行的标记位都不匹配,所以,未命中。而后,高速缓存从内存中取出块0,块1, 共2字节,并存储在组0第一行中。具体以下图所示。
2. 读地址1的数据。<font color=#FF4500 size=3>标记位为00,索引位为0,偏移位为1,块号为1。</font>缓存行中已有数据数据,组0的第一行有效位为1,地址1的标记位和组0的第一行标记位匹配,所以,命中。具体以下图所示。
3. 读地址7的数据。<font color=#FF4500 size=3>标记位为01,索引位为1,偏移位为1,块号为1。</font>缓存行中有数据,组1的有效位为0,地址的标记位和组1中的第一行和第二行的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块6,块7, 共2字节,并存储在组1中。具体以下图所示。
4. 读地址8的数据。<font color=#FF4500 size=3>标记位为10,索引位为0,偏移位为0,块号为0。</font>缓存行中有数据,组0的第一行有效位为1,第二行有效位为0,地址的标记位和组0的第一行和第二行的标记位不匹配,所以,未命中。而后,高速缓存从内存中取出块8,块9, 共2字节,并存储在组0的第二行中。具体以下图所示。
5. 读地址0的数据。<font color=#FF4500 size=3>标记位为00,索引位为0,偏移位为0,块号为0。</font>缓存行中有数据,组0的第一行有效位为1,地址的标记位和组0的第一行的标记位匹配,所以,命中。具体以下图所示。
地址 | 二进制 | 是否命中 |
---|---|---|
0 | [${0000_2}$] (t=00,s=0,b=0) | 否 |
1 | [${0001_2}$](t=00,s=0,b=1) | 是 |
7 | [${0111_2}$](t=01,s=1,b=1) | 否 |
8 | [${1000_2}$](t=10,s=0,b=0) | 否 |
0 | [${0000_2}$](t=00,s=0,b=0) | 是 |
两路相联高速缓存与直接映射高速缓存相比,在每组中增长了一行,缓存命中率提高了15%。避免了缓存频繁从内存中存取数据的状况,提升了程序运行速度。
全相联高速缓存中的行匹配和字选择与组相联高速缓存中的是同样的,过程就再也不赘述,其结构图以下所示。
相联度越高越好吗?答案是否认的。较高的相联度会形成较高的成本。<font color=#FF4500 size=3>实现难度大,价格昂贵,并且很难使之速度变快。</font>较高的相联度会增长命中时间,由于复杂性增长了,另外,还会增长不命中处罚,由于选择牺牲行的复杂性也增长了。
相联度的选择最终变成了命中时间和不命中处罚之问的折中。通常来说,<font color=#FF4500 size=3>高性能系统会为L1高速缓存选择较低的相联度</font>(这里的不命中处罚只是几个周期),而在不命中处罚比较高的较低层上使用比较小的相联度。例如, Intel Core i7系统中,L和L2高速缓存是8路组相联的,而L3高速缓存是16路组相联的。
在此以前,咱们一直假设高速缓存只保存数据。不过,实际上,高速缓存既保存数据,也保存指令。只保存指令的高速缓存称为<font color=#0000FF size=3> i-cache </font>。只保存程序数据的高速缓存称为 <font color=#0000FF size=3> d-cache </font>。既保存指令又包括数据的高速缓存称为 <font color=#0000FF size=3> 统一的高速缓存 </font>。
以下图所示为 Intel Core i7处理器的高速缓存层次结构。每一个CPU芯片有四个核。每一个核有本身的L1 i-cache, L1 d-cache和L2统一的高速缓存。全部的核共享片上L3统一的高速缓存。其具体参数以下表所示。
缓存 | 大小 | 内部结构 | 访问时间 |
---|---|---|---|
L1 | 32KB | 8路相联 | 4时钟 |
L2 | 256KB | 8路相联 | 10时钟 |
L3 | 8M | 16路相联 | 40-75时钟 |
最后介绍下衡量高速缓存性能的一些指标:
在一个程序执行或程序的一部分执行期间,内存引用不命中的比率,它等于:<font color=#0000FF size=3> 不命中数量/引用数量。 </font>
命中的内存引用比率。它等于:
从高速缓存传送一个字到CPU所需的时间,包括组选择、行确认和字选择的时间。通常来说,L1缓存的命中时间为:4个时钟。L2缓存的命中时间为:10个时钟。
未命中须要的额外时间。对于主存来讲,通常为<font color=#0000FF size=3> 50 ~ 200个时钟周期。 </font>
举个例子:假设缓存命中时间为1个时钟周期,缓存未命中惩罚为100个时钟周期。
下面计算下97%缓存命中率和99%的缓存命中率的平均访问时间为多少?计算公式为命中时间加上未命中处罚乘以百分系数。
97%的命中率:$1 + 0.03 \times 100 = 4$时钟。
99%的命中率:$1 + 0.01 \times 100 = 2$时钟。
<font color=#0000FF size=3> 结论:命中率增长2%,平均访问时间减小了50%。 </font>
计算机中存在着各类各样的缓存,好比,<font color=#0000FF size=3> 文件缓存 </font>把一些须要高速存取的变量缓存在内存中,每次访问直接读出便可。<font color=#0000FF size=3> 浏览器缓存 </font>根据一套与服务器约定的规则进行工做,若是在浏览过程当中前进或后退时访问到同一个图片,这些图片能够从浏览器缓存中调出而即时显示。<font color=#0000FF size=3>数据库缓存 </font>常常须要从数据库查询的数据、或常常更新的数据放入到缓存中,这样下次查询时,直接从缓存直接返回,减轻数据库压力。
咱们了解这么多基本概念有什么用呢?若是咱们理解了计算机系统是如何将数据在内存中组织和移动的,那么在写程序时就能够把数据项存储在合适的位置,CPU能更快地访问到它们,提升程序的执行效率。
下一篇文章咱们将介绍<font color=#0000FF size=3>如何写出高效的代码,让程序运行的更快! </font>欢迎关注个人公众号,第一时间获取更新!
养成习惯,先赞后看!若是以为写的不错,欢迎关注,点赞,在看,转发,谢谢!
如遇到排版错乱的问题,能够经过如下连接访问个人CSDN。
**CSDN:[CSDN搜索“嵌入式与Linux那些事”]