在一切开始之前,首先最重要的是需要去明白和掌握内存的块的定义:内存被分为若干块,这些块:1.大小相等,2.每块由若干字组成,3.块的长度成为块长,块的长度是指由几个字组成就是多长,比如一个块由x个字组成,那么块长为x.4.每个块由连续的字组成。在Cache中这种块被某些替换原则替换进入Cache之后,称为Cache的行(有些书上也称为块,这里就用行表示,以免混淆)。有以下几个特点:
1.块长(一般是取一个存取周期内从主存调出的信息长度,和交互存取有关系)与行长相等,这点和物理页和逻辑页的页内地址的长度是一样的原理类似。
2.Cache中行的位数=行号+行内地址 内存中块的位数=块号+块内地址。
3.块内地址的位数由块的长度决定(设块的长度为x,块内地址的位数其实就是用几位能表示x的问题),块号的位数由块的数目决定(比如块的数目是x,块号其实就是用几位二进制数能表示x个数的问题),行号的位数由行数决定。块里的内容就是行的内容,行内地址和块内地址相同。、
标记位是什么?先记住一点,标记位和cache的地址没有一点关系。每个行对应一个标记位。后面会进行说明
CPU与内存以及Cache之间的交互方式
好了,明白了块和行的定义之后,我们来看看CPU与内存和Cache之间的交互:1.CPU同时(也有可能不是同时,这时事先访问cache cahce里面没有再对主存进行访问,如果缺失的话访存时间会长一点)向Cache和内存发出读请求。把地址同时送给Cache和内存。2.Cache控制逻辑(由硬件实现)判断此内存地址是否在Cache中,在则立马将此内存的字送给CPU,与此同时,终止访问内存。3.若不在Cache中,用主存读取周期从主存中将字取出送往CPU,与此同时,把含有这个字的整个数据块通过Cache与主存的直接通路送到Cache中。(由这个交互过程我们可以看到,主存和Cache与CPU交互的时候传送的是字,但是Cache和主存交互传送的是块)
了解了过程之后我们来看看映射关系是什么,映射关系是CPU在访存时,将主存地址变换成Cache地址的过程。对应第二步的判断是否在Cache中的其中一部。
三种映射关系。1.直接映射。2.全相联映射。3.组相联映射。
1.全相联映射
将块内地址直接变为Cache的行内地址,同时将块号直接放进Cache的标记位(这个时候标记位就是内存地址的块号)里去,直接将主存里的一个快包括内容直接拷贝到Cache里的任意一行里去(一般是弄成连续的),这样相当于相当于建立了一个标记位和行之间的对应关系表,访问一个内存地址上的字,只要从内存地址中取出块号,然后在标记位和行之间的对应关系里查表,就能找到这个字所在的行,然后用快内地址(其实就是快内偏移量)找到行中对应的字的地址。
例如,主存现有256个块(得出块号为8位),每个快有256个字(得出块内地址8位),所以内存地址总位数为16位,所以cache块内地址应该有8位,设它有8行,行号有3位,所以cache地址长度为3 + 8 =11位,标记位为块号。可以随便映射到任意一个Cache的行,建立这个块号(也就是标记位)与映射到的行的对应关系,标记位的内容为块号(即内存地址的前八位),这个就是简单的全相联映射。 此时,主存容量为块数(256)*块长(256)为64K,Cache容量为行数 (8)*行长(256)为2K(不包括标记位)。(为什么是K 不是KB?其实写成64KB 2KB也可以,毕竟表示的是容量,写成64K 2K就意味着,有64个字的容量,或者有2K个字的容量,因为有些计算机一个字可能不是8位。本例子中是一个字8位)
如何访问内存中一个字怎么找到他对应的cache的地址呢?(由硬件实现),有一个比较器,1.CPU访问按内存地址,将其块号放到比较器中,然后比较器将块号和标记位与行之间的对应关系表中的标记位一个一个比较,如果有一个标记位和块号一样,则说明这个字所在的块在Cache里,然后用地址的低八位(快内地址,也叫做快内偏移量)找到行中的那个字的地址 然后读入CPU。2.如果没有标记位和块号一样,说明不在Cache里,通过16位地址直接访问内存送CPU,同时将这个字所在的块整个搬入Cache里(硬件实现)。这种方法命中率高,但是比较器电路设计复杂。因为对应关系表中一个标记位就要连一条线,最简单的比较器就是两个数比较最简单,所以有了直接映射。
举个例子:比如 addr1:0010 1101 0010 1110 addr2:1101 1111 1111 1010 cache 有8行。
划分内存地址 红色为标记位,橙色为块内偏移量。刚开始cache为空,访问addr1 addr2,不命中,从内存读取块,开始全相联映射,比如addr1,他属于块0010 1101 0000 0000 —– 0010 1101 1111 1111,随机放入一行中,假设放入了101行,那么101行 也就是101 0000 0000 —– 101 1111 1111这一行的标记位就为 0010 1101,同理 假设addr2放入的行是111,那么标记位就 为 1101 1111,建立标记位和行的对应关系,相当于打表,表中有两项,0010 1101 对应 101 0000 0000,1101 1111对应 111 0000 0000 .第二次访问addr2的时候,遍历这张表,找到标记位与addr2前八位相同的表项 ,取出行号111 ,再与其块内偏移量一拼,就得到了cache中的地址111 1111 1010,这个地址上就是我们要访问的字。
2.直接映射
指的是 主存里的一个块能通过一个式子的映射能放在Cache中的一个特定的行里(这个逻辑由硬件实现)Cache的行号 i 和主存块号j存在关系 i=j mod n n为总行数,比如43号块,cache中有10行,说明43 必须放入第行号为3的那一行行,而且23 13 53等都是这样。
例子:
内存地址16位,Cache行数8行,内存地址低八位位块内地址,高八位位块号。这时,块号8位中的低三位,其实是块号除于八的余数(000-111),所以可以直接用内存地址当中的这三位来表示cache的行号,都不用算 ,结果剩下5位是标记位,内存的块往cache放的时候,内存地址的高八位中的的低三位是多少就往第几行放(硬件实现),吧剩下五位当做标记位,建立标记位与行的映射关系,便于查找。
原理。拿到16位地址之后,取出高八位的低三位确定行号去找第几行。吧这一行的标记位拿出来通过比较器和地址前五位比较,如果一样,则命中,不一样不命中,若命中,再根据块内地址(快内偏移)找到字送给cpu,不命中,访问内存,把包含这个字的整个块放到刚才我们找的那一行里面去(硬件实现)
比较器简单,只用比较两个数,但是不灵活,会产生抖动,冲突率最高,空间利用率最低。同时注意不需要替换算法。
举个例子:比如 addr1:0010 1101 0010 1110 addr2:1101 1111 1111 1010
两个内存地址:划分内存地址 红色为标记位,蓝色为行号,橙色为块内偏移量,cache一开始为空的时候 访问这个两个地址,不命中,开始直接映射,内存地址划分之后红色表示的是标记位,蓝色表示的是组好,黄色表示的是块内地址,那么addr1就是应该映射到行号为101的cache行,意思是整块映射到地址为101 0000 0000 —–101 1111 1111的cache行中, addr2 映射到行号为111的cache行,意思是整块映射到地址为111 0000 0000 —– 111 1111 1111的行中,然后建立起标记位和行的对应关系,00101 对应101 0000 0000 (相当于把这一行的起始地址与标记位对应) 11011 对应111 0000 0000 ,当访问addr1的时候 获得组号10,查表,通过硬件直接得到 101行的标记位00101,命中,然后快内偏移位0010 1110 直接与101 连接起来 得到最终要的要访问的字所在的cache地址 101 0010 1110 。
3.组相联映射
是折中的方法,将cache的8行分为4组,没组2行,这叫做2路组相联,(一组4行叫做,4路组相联)。然后用内存地址的块号处于总组数,余数是几就往第几组放,组里面随便放。组内相当于全相联,组间直接映射。
内存地址,16位分隔为3部分:6位(直接作为标记位)+2位(用来确定组号)+块内8位。看组号是几,就放第几组,组里面随便放。将剩下6位当做cache标记位。二路组相联比一路的组号少一位,4路的少二位。。。类推
查找过程:获得16位内存地址,由组号是几就找第几组,将里面两个行的标记位和内存地址的前6位比较,如果有一个一样,说明命中。由块内地址找到字。两号标记位都不一样,就在内存中读出数据同时将字所在的整个块放到我们刚才找的那个组里面去,两行随便放(硬件实现)
举个例子:比如 addr1:0010 1101 0010 1110 addr2:1101 1111 1111 1010
两个内存地址:红色表示的是标记位,蓝色表示的是组好,橙色表示的是块内地址,第一次访问addr1 addr2 未命中,将内存块放入cache,那么addr1就是应该映射到组号为01的组,也就是第一组 意思在地址为010 0000 0000 —– 010 1111 1111的cache行和地址为 011 0000 0000 —– 011 1111 1111的cache行中, addr2 映射到组号为11的cache行,也就是第3组 意思是在地址为110 0000 0000 —– 110 1111 1111的行以及111 0000 0000 —– 111 1111 1111的行中。组内随便放,比如addr1 ,他所在的块0010 11 …… 放入的是011 0000 0000 —- 011 1111 1111的cache行中,那么他的这一行的标记位为0010 11,进建立对应关系,下次访问addr1的时候 直接得到组号,然后找到组好对应的两个行,取出标记位,与红色区域进行对比,然后与块内地址一拼接,就得到了字所在的地址。
计算问题
在cache的试题当中,我们会发现很多寸步难行的地方,主要的原因还是对于cache的地址的不了接所造成的。比如要你求cache的总容量你会怎么求呢。比如告诉你cache的总容量为16KB你就会说这么简单,不就是cache的地址是14位吗?的确有道理,因为不管你划分什么鬼标记位,块内地址,甚至是什么片选信号位,以及页号等等等,其实实质上都是一个地址对应一个字的数据,比如1100 1100 0011 0101这个地址,你无论怎么划分标记位什么的,其实就是相当于给整个存储系统分了几个部分。仅此而已,所以有道理。但是其实错了,为什么,这下我要告诉你cache的地址的真正构成,这个地方是本人经过很多题目的训练和网上寻找答案总结出来的东西,都是干货
大家都知道cache中的一行由标记位和块内地址组成,但是大家知道吗,其实一行中,有很多个字节,但是标记位只有一个,而不是每个字都对应一个相同的标记位。什么意思呢?就是cache的总容量首先有块内地址的容量,比如一个行长是64B的cahce,他的块内地址的容量就是512bit,这叫做每行存储的数据,也叫做数据块的位数但是在这一个快中,只有一个标记位,这个标记位,其实实质上不属于cache的地址。只是一个内容。(详情见王道P119)所以
cache的总容量=(有效位(每个行一般会加上一个有效位)+脏位+替换控制位+标记位的位数+数据块总位数。(一般题目会要求“若不考虑用雨cache的一致性维护和替换算法的控制位”,如果没有这个条件,但是他也没说写策略用的是哪个,或者没说替换策略用的是哪一个,那么久当做只有 有效位来处理))X行数;
所以,如有上面那句话,
cache的总容量=(有效位(每个行一般会加上一个有效位)+标记位位数+数据块总位数)X行数。
例:块长(有些书上称行为快)为64B(行长一般是K做单位,表示字的个数,但是题目有时候会用B ,bit KB MB这个意思就是一个行的有效容量,也就是一般所说的容量,还有数据块的位数),主存256MB,按字节编址(一般默认是这样,除非题目说明了按字编址或者说是出现了aMXbB这样的描述内存大小的方式)。有8行。求cache总容量和容量(若不考虑用雨cache的一致性维护和替换算法的控制位)
第一步一般求主存地址位数:主存256MB可得,256M=2的28次方,所以主存地址位数为28位。
第二部一般就块内地址位数:很可能用到行长来反推块长,因为两者相等。所以行长=块长=64,所以块内地址为6位。
第三部求标志位数(这个指的是主存地址的标志位,所以不用加1):28-行号位数(3位)-6(块内地址)=19位。
第四部求出cache行标记位的组成:19位+1,一定要注意加上有效位。
第五步求总容量=(20+64×8)x8
容量很简单1.块长x行数=64Bx8,
注意一般为cache的地址就是cache的块内地址。因为这才是他的有效数据位,还有问的是主存的标志位还是cache的标志位,还有就是块号和行号以及第几块一般是从0开始(一般题目会说,但是也可能不会说)
有一种情况就是告诉你cache的容量为16B这下你可以直接得出cache的地址位数为4位,如果是总容量就不行
关于最常考的关于c语言与cache的映射结合起来的方式,就是循环和求缺失率:
比如for(in=0;i<100;i++)
a[i]=a[i]+k;
这种就是,这里相当于每执行一次赋值语句,就访问两次a[i],比如如果是int类型的数组,一个数4B,但是块的长度为16B,就是说一个快中有4个a数组的数,这时候他会问你,如果求cache的访问缺失率,主要是一点,不要去算总的访问次数和缺失次数或者命中次数,一次走那个语句的命中率久可以代表所有的命中率或者缺失率,这里假设一开始cache为空,为直接映射,他访问a[0]时候 ,未命中,于是到内存里将含有这个数的整个块调入cache中,所以就把这个数a[0]以及后面三个数a[1] a[2] a[3]全部掉入了cache里,所以每访问四个数,就只有第一次访问是缺失的,后面的7次都是命中,四个数总共访问8次a数组,所以命中率为1/8.