存储器概念
存储器分类
- 存储器是用来存储程序和数据的部件。
- 对于计算机来说,有了存储器才有记忆功能,才能正常工作。
- 按用途可分为主存储器和辅助存储器。
- 主存储器通常安装在主板上,包括:只读存储器ROM(例如主板上的用来保存BIOS信息)和随机存储器RAM。RAM存放当前CPU正在执行和将要执行的程序和需要处理的数据,数据断电后丢失(我们常说的内存条就是RAM)。
- 辅助存储器包括:硬盘、磁带、光盘、U盘等。辅存用来弥补主存容量不足,且可以长期保存数据。
主存储器特点
- 存储单元是CPU访问存储器的基本单位,以8位二进制作为一个存储单元。
- 每个存储单元有一个地址,一般用十六进制数表示。
- 程序中的变量、函数、数组、对象等都要放在主存的存储单元中。
- 存储单元特点:只有往存储单元送新数据时,该存储单元的内容才会被新值代替,否则将保持原有数据。
指针概念
- 指针即地址。
- 一个变量在主存中占若干个连续的存储单元。比如:C 语言中float占4个字节,double占8个字节,long double 占16个字节
- 对于一个函数或者对象来说,在主存中需用一片连续的空间来进行存储,这片连续空间首字节的地址即为函数或对象的指针。计算机通过首地址就可以访问到该函数或对象。
- 下图所示程序中变量a的指针为0x0077ffe6。通过该指针可得到a中的值。
例:java某方法中写了如下的语句:
int a = 35;
Company c1 = new Company(1000,“Xiu Zhen Yuan“);
则上述代码段在内存中的分配情况为:
java 内存分配
栈内存用来存放 函数(方法)中定义的 一些基本类型的值或者对象的引用地址。
堆内存用来存放 由new创建的对象和数组
若又有语句:
Company c2 = c1;
以上即为复制引用,修改了c1中的值,c2中的值也会被修改,因为二者本质上指向同一片内存区域。
Company c2 = (Company)c1.clone();
以上即为复制对象,C1和C2 在内存中占据2片不同的存储空间,互不干扰。
存储器分层结构
按功能划分为主存储器(内存)、辅助存储器(外存)、高速缓存(Cache)。
用户需求的矛盾:
内存很快但是比较贵,用户既不想花钱又想要大存储。
计算机局部性原理
在CPU访问寄存器时,无论是存取数据还是存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。
- 时间局部性—被访问过的信息,可能很快被再次访问
- 空间局部性—被访问信息的相邻信息,可能很快被访问
根据局部性原理产生的解决方案:
- 近期常用数据—放在“前方”MEM(快而小)中
- 近期不用数据—放在“后方”MEM(慢而大)中
层次存储系统组成
- 思想:①用多种类型MEM构成前方-后方的层次结构;
- 前方MEM中信息为后方MEM中信息的副本;
- 各层MEM之间信息传递是“透明”的
常见的存储系统层次结构
围绕主存的层次结构一般为 “Cache-主存-辅存” 三种MEM 构成的 两个存储层次
Cache-主存”存储层次:
CPU的速度是极快的,大约是内存读写的10倍以上,因此需要设置 高速缓冲存储器(Cache)
利用 Cache的速度,主存的容量
“主存-辅存”存储层次:
可能存在:所执行的程序空间>=主存空间,所以需要增加辅存 解决 主存容量问题
利用 主存的速度,辅存的容量
层次存储系统的工作方式
即将执行的指令和数据存放在主存中
层次存储系统的工作方式如下图:
高速缓冲存储器(cache)
cache 工作原理
- 为了解决CPU和主存之间速度不匹配问题。
- 将Cache和主存分成若干个块(行),每块包含若干个字。
之前提到了 “计算机局部性原理”,Cache便是依赖此理论进行如下操作,当CPU需要读取某个地址的数据时,根据这个地址,先判断Cache中是否已经有缓存了,有就直接从Cache中取,否则从内存中取 这个地址所在的一块连续数据, 将内存中这个数据块存入Cache
举例子:
内存地址: 00,01,02,03, 10,11,12,13 , 20,21,22,23 (根据前一位区分为3个块)。 Cache:NULL
读地址21 -> Cache中不是这个块,读取块并插入Cache -> Cache :20,21,22,23
读地址22 -> Cache中正是这个块,直接从Cache中读取 22
读地址23 -> Cache中正是这个块,直接从Cache中读取 23
利用这个方式,在读取相邻数据和重复数据时Cache就起到了很大的加速作用。实际过程比上面要复杂一些, 地址映射 和 替换策略,将会有多种策略
地址映射
- 地址映射即把CPU送出的主存地址转换成Cache存储器的地址。
- 有三种方式:直接映射、全相连映射、组相连映射。
- 对于每一种映射方式,需要搞清楚主存中的某一块放到Cache中的哪个位置,然后思考主存地址被分成几个部分,Cache地址被分成几个部分,需要保存什么信息以判定某个主存地址命中与否。
1、直接映射
主存空间按照Cache容量分区,规定主存中每个块都限定了必须在Cache中的某个块中,也就是第0、1…M-1区的第0块都只能放在Cache的第0块中。
查看区表存储器中的区号是否和主存中的区号相等,相等则存在即命中,否则不命中。
直接映射特点:地址变换简单、但是不灵活,冲突高。
2、全相连映射
主存中的任意一块可以调入Cache存储器中的任何一块中。
全相连映射特点:块冲突率低,但地址变换机构复杂,速度慢,成本高。
3、组相连映射
将Cache中的块分成组。主存按照Cache容量分区,区内分组。规定组间采用直接映射方式而组内采用全相联方式。每组内有2、4、8、16块,称为2路、4路、8路、16路组相联Cache。
组相连映射特点:块的冲突概率比较低,块的利用率大幅度提高,块失效率明显降低。2路、4路的组相连在命中率和成本方面相对较好。
通过例题理解Cache地址映射
例1、某 32 位计算机的 cache 容量为 16KB,cache 块的大小为 16B,若主存与 cache 的地址映射采用直接映射方式,则主存地址为 1234E8F8(十六进制)的单元装入的 cache 地址为____。
A. 00 0100 0100 1101 (二进制)
B. 01 0010 0011 0100 (二进制)
C. 10 1000 1111 1000 (二进制)
D. 11 0100 1110 1000 (二进制)
解析:
Cache容量16KB,每块16B。所以Cache被分成 16000 / 16= 1000 = 10^2 个块。
注意上面的图片,Cache中的每一个块地址都需要切割成两部分 块号 和 块内地址。
一个块的大小为 16B ,里面就包含了16个存储单元,我们就需要 二进制数的4位长度 才能够记录 0-15 (1111),
刚刚计算得到 总块数为 1000 ,那么就需要 二进制数10位长度 才足够记录所有的块数, 0-1000 (1111111111)
直接映射之所以快就是因为,主存中的 块号和块内地址 与 Cache 中的是完全一样的,可以直接找到对应区的数据。
主存地址可切割为 区号、块号、块内地址,Cache地址可切割为 块号、块内地址 。我们刚刚计算出了 块号的长度为 4,块内地址的长度为 10。因此可以解题,1234E8F8 转化为 二进制 = 10010001101001110100011111000。那么cache地址就是主存地址的后 4+10 = 14 位。
例2.容量为64块的Cache采用组相联方式映像,字块大小为128字节,每4块为一组,若主存容量为4096块,且以字节编址,那么主存地址为(____)位,主存区号为(____)位。
解析:
主存有 4096块,每块 128字节,所以主存的总大小为 4096 * 128 = 2^12 * 2^7 = 2^19 。
由上得知 主存地址为 19位。
因数值较大,我们就不像例1一样求出具体的容量值了,使用以2为底数的指数形式能清晰展示 记录该数值需要多少位的 二进制数长度
主存容量为4096块,Cache为64块,因此主存被分为 4096 / 64 = 2^12 / 2^6 = 2^6 个区,用 二进制数6位长度 做 区数 的计数
由上得知 主存区号为 6 位。
替换策略
- 先进先出算法(FIFO)
- 最近最久未使用算法(LRU)
因为Cache的大小有限,因此主存的装入其实是一个不停覆盖的过程。根据历史贡献 LRU算法在此操作中要更优。
FIFO算法
按照“先进先出(First In,First Out)”的原理淘汰数据,正好符合队列的特性,数据结构上使用队列Queue来实现。(Cache的替换策略是由硬件实现的,这里是打个比方)
- 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动;
- 淘汰FIFO队列头部的数据;
LRU(Least recently used,最近最少使用)算法
根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
- 新数据插入到链表头部;
- 每当Cache命中(即Cache中数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃。
例题:假如有一个容量为4的Cache,采用全相连映射方式,若访问主存块号的序列为1,2,3,4,2,1,5,6,2,1,2,3,6,3。分别采用FIFO和LRU算法后,画图算出两种算法的未命中次数。