本节讲述存储器的分类结构和功能
存储器分类
- RAM
- ROM
- 磁盘
- 闪存盘(在外存区域使用的可读写ROM)
RAM
-
随机访问存储器分为两类:静态(SRAM)和动态(DRAM),静态的比动态的更快,但是也更贵,SRAM常常用作高速缓存存储器,DRAM常用作主存以及图形系统的帧缓冲区。
-
SRAM:SRAM将每个位存储在一个双稳态的存储器单元里,每个单元是用一个六晶体管电路来实现的。这个电路有这样一个属性:
它可以无限期地保持在两个不同的电压状态之一
。其他任何状态都是不稳定的,电路会迅速转移到两个稳定状态中的一个。- 由于SRAM的双稳态特性,只要有电,它就会永远地保持它的值。
SRAM对干扰不敏感。
- 由于SRAM的双稳态特性,只要有电,它就会永远地保持它的值。
-
DRAM:DRAM将每个位存储为对一个电容的充电。和SRAM不同,DRAM单元对干扰非常敏感。 存储器系统必须周期性地通过读出,然后重写来刷新存储器的每一位。
-
传统的 DRAM
DRAM 芯片被分为 d 个超单元,每个超单元包含 w 个 DRAM 单元,w 一般为 8。当从 DRAM 中读取数据时,一次可以读取一个超单元的数据(可以近似的将超单元理解为一个字节)。
DRAM 中的超单元按行列组织,DRAM 中还包含一个行缓冲区。
内存控制器依次将行地址和列地址发送给 DRAM,DRAM 将对应的超单元的内容发回给内存控制器以实现读取数据。
行地址和列地址共享相同的 DRAM 芯片地址引脚。
从 DRAM 中读取超单元的步骤:
- 内存控制器发来行地址 i,DRAM 将整个第 i 行复制到内部的行缓冲区。
- 内存控制器发来列地址 i,DRAM 从行缓冲区中复制出超单元 (i,j) 并发送给内存控制器。
-
许多 DRAM 芯片封装在内存模块中,插到主板的扩展槽上。
常用的是双列直插内存模块 (DIMM),以 64 位为块与内存控制器交换数据。
比如一个内存模块包含 8 个 DRAM 芯片,每个 DRAM 包含 8M 个超单元,每个超单元存储一个字节。使用 8 个 DRAM 芯片上相同地址处的超单元来表示一个 64 位字,DRAM 0 存储第一个字节,DRAM 1 存储第 2 个字节,依此类推。
要取出内存地址 A 处的一个字,内存控制器先将 A 转换为一个超单元地址 (i,j),然后内存模块将 i,j 广播到每个 DRAM。作为响应,每个 DRAM 输出它的 (i,j) 超单元的 8 位内容,合并成一个 64 位字,再返回给内存控制器。
主存由多个内存模块连接到内存控制器聚合成。
-
以下是SRAM与DRAM的比较:
-
增强的DRAM
快页模式DRAM,扩展数据输出DRAM,同步DRAM,双倍数据速率同步DRAM,视频RAM等等。- DRAM集成了更好的接口逻辑、更快的I/O传输接口
- 同步DRAM (SDRAM)
- 使用与内存控制器相同的时钟信号,取代异步控制信号
- 允许行地址复用 (比如 , RAS, CAS, CAS)
非易失性存储器(ROM)
- 如果断电,DRAM和SRAM会丢失它们的信息,从这个意义上说,他们是易失的。非易失性存储器即使在断电后仍然保存着他们的信息
- 可编程ROM(PROM):只可以被编程一次。
- 可擦写可编程ROM(EPROM):EPROM和电子可擦除PROM都可以被多次擦除和编程。
- 闪存(flash memory):提供相对于传统磁盘的一种更快速更强进和更低消耗的选择。
总线
-
一条总线(bus)是由多条并排的导线组成一束线, 其传输地址、数据和控制信号
-
多个设备共享条总线
-
下图展示了一个典型计算机系统的配置:
-
其中,I/O桥是一组芯片,将系统主线的电子信号转换为内存总线的电子信号
-
I/O桥也将I/O总线的数据回流到系统总线中。
-
主存和CPU间传输信息的过程
每次CPU和主存之间的数据传送都是通过一系列步骤完成的。这些步骤称为总线事务
。从主存传数据到CPU是读事务
,从CPU到主存是写事务
读事务
以movq A,%rax
为例:
内存地址A的内容被加载到%rax里面,过程如下图所示:
写事务
以movq %rax,A
为例,%rax里的内容被加载到A里面:
磁盘
由于软盘已经淘汰,本节主讲机械硬盘:
磁盘的构造
磁盘是由盘片构成的,每个盘面有两面或者称为表面,表面覆盖有磁性材料,盘片中央有一个可旋转的主轴,使得盘片以固定旋转速率旋转。
磁盘通常包含一个或者多个这样的盘片并封装在一个密封容器中。
如上图,每个表面由一组称为磁道
的同心圆组成,每个磁道被划分为一组扇区
。每个扇区包含相等数量的数据位,这些数据编码在扇区上的磁性材料中。扇区之间由一些间隙
分隔开,这些间隙中不存数据位,而用来标志扇区的格式化位。磁盘由一个或多个叠放在一起的盘片组成,封装在密封包装中。整个装置简称为磁盘
,也称为旋转磁盘
,以使之区别于基于闪存的固态硬盘
。经常用柱面
来描述多个盘片驱动器的构造,这里,柱面是所有盘片表面上到主轴中心的距离相等的磁道的集合。例如,如果一个驱动器有3个盘片,即6个面,每个表面上的磁道的编号都一致,那么柱面k就是6个磁道k的集合。
磁盘的容量
-
磁盘容量由以下技术因素决定:
- 记录密度 (Recording density) (位/英寸 ): 磁道一英寸的段中 可放
入的位数。 - 磁道密度 (Track density) (道/英寸 ): 从盘片中心出发半径上 一英寸的段内可以有磁道数。
- 面密度 (Areal density) (位/平方英寸 ): 记录密度 与磁道的乘积。
- 记录密度 (Recording density) (位/英寸 ): 磁道一英寸的段中 可放
-
分区记录
**现代磁盘将所有道划分为若干组( recording zone) **,组内各磁道相邻- 区域内各磁道的扇数目相同, 扇区数取决于区域内最内侧磁 道的圆周长
- 各区域的每磁道扇数都不同 , 外圈区域的每磁道扇数比内圈区 域多
- 所以我们使用每磁道平均扇区数来计算盘容量
-
容量 =(字节数/扇区)×(平均扇区数/磁道 )× (磁道数/面)×(面数/盘片 )×(盘片数/磁盘 )
-
Example: 512 字节/扇区、300 扇区/磁道 (平均值 ) 、20,000 磁道/面 、2 面/盘片、5 盘片/磁盘
容量 = 512 x 300 x 20000 x 2 x 5 = 30,720,000,000 = 30.72 GB
-
磁盘的访问方式
磁盘访问时间
-
访问目标扇区的平均时间大致为:
- 访问时间 = 寻道时间 + 平均旋转延迟 + 数据传输时间
-
寻道时间 (Seek time)
-
读/写头移动到目标柱面所用时间
-
通常寻道时间为: 3-9 ms
-
-
旋转延迟(Rotational latency)
-
旋转盘面使读 /写头到达目标扇区上方所用时间
-
平均旋转延迟= 1/2 x 1/RPMs x 60 sec/1 min (RPM: 转 /分钟 )
-
通常RPMs= 7200
-
-
数据传输时间(Transfer time)
-
读目标扇区所用时间
-
数据传输时间= 60s/RPM x 1/(平均扇区数/磁道) x 60 secs/1 min.
-
-
总时间=寻道时间+旋转延迟+传输时间
-
例子:
磁盘逻辑块
-
现代磁盘以简单的抽象视图来表示复杂构造:
- 磁盘被抽象成 b个扇区大小的逻辑块 (logical block) 序列 (编号为 0, 1, 2, …)
-
逻辑块与物理扇区之间的映射关系
-
由磁盘控制器维护
-
磁盘控制器将逻辑块号转换为一个三元组 (盘面, 磁道, 扇区 )
-
-
允许磁盘控制器为每个分区预留一组柱面作备份
-
区分“格式化容量”与“最大容量
I/O总线和磁盘访问方法
硬盘的访问走的是I/O总线
硬盘的访问方式:
固态硬盘
SSD 的性能特性
-
顺序访问比随机访问快
- 典型的存储器层次结构问题
-
随机写较慢
-
擦除块需要较长的时间(~1 ms)
-
修改一页需要将块中所有页复制到新的块中
-
早期SSDs读/写速度之间的差距更大
-
-
相比机械磁盘硬盘
-
优点:
没有移动部件–>更快、能耗更低、更结实
-
缺点
- 会磨损
- 闪存翻译层中的平均磨损逻辑试图通过将擦除平均分布在 所有块上来最大化每个块的命
- 比如,Intel SSD 730 保证能经得起 128 PB (128 x 1015 字节) 的写
- 2015年,SSD每字节比机械磁盘贵大约30倍,目前约10倍
- 会磨损
-
存储技术的变化
存储单价降低
各级存储的速度差增大
存储器分层
局部性原理
局部性原理(Principle of Locality): 程序倾向于使用距离
最近用过的指令/数据地址相近或相等的指令/数据
- 时间局部性 (Temporal locality):
§ 最近访问过的信息,很可能在近期还会被再次访问 - 空间局部性 (Spatial locality):
§ 地址接近的数据项,被使用的时间也倾向于接近
举例:
sum = 0;
for (i = 0; i < n; i++)
sum += a[i];
return sum;
- 对数据的引用
- 顺序访问数组元素 (步长为1的引用模式)——空间局部性
- 变量sum 在每次迭代中被引用一次——时间局部性
- 对指令的引用
- 顺序读取指令——空间局部性
- 重复执行for循环体——时间局部性
对局部性的定性评价
- 断言: 能通过查看程序代码,对程序局部性有定性的认
识,是专业程序员的一项关键技能. - 例题:
缓存
当我们说“缓存”时在说什么
一般而言,高速缓存(cache,读作“cash”)是一个小而快速的存储设备,它作为存储在更大、也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存(caching,读作“cashing")。
存储器层次结构的中心思想是,对于每个k,位于k层的更快更小的存储设备作为位于k+1层的更大更慢的存储设备的缓存。换句话说,层次结构中的每一层都缓存来自较低一层的数据对象。
例如,本地磁盘作为通过网络从远程磁盘取出的文件(例如Web页面)的缓存,主存作为本地磁盘上数据的缓存,依此类推,直到最小的缓存——CPU寄存器组。
缓存的一般性概念
注意:以下的层数是从CPU到外存逐级递增的
第k + 1层的存储器被划分成连续的数据对象组块,成为块。
每个块都有唯一地址或名字,每个块可以固定大小,也可以变大变小。第k层的存储器被划分为比较少的块集合,每个块的大小与k + 1层的块的大小一样。在任何时刻,第k层的缓存包含第k + 1层块的一个子集的副本。
**数据总是以块大小为传送单元在第k层和第k + 1层之间来回复制。**在L1到L0之间通常使用的是1个字大小的块,L2和L1之间(L3和L2之间、L4和L3之间)通常是几十个字大小的块,L5和L4之间是大小为几百或几千字节的块。层次结构距CPU越远的设备访问时间越长,因此为了补偿这些较长的访问时间,倾向于使用较大的块。
-
缓存命中
当程序需要第k + 1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d,如果d刚好缓存在第k层中,就称为缓存命中。那程序就直接从缓存中读取d,比去第k + 1层读d更快。 -
缓存不命中
如果第k层没有缓存数据对象d,就称为缓存不命中。不命中时,第k层的缓存从第k + 1层缓存中取出包含d的那个块,如果第k层的缓存已经满了,可能就会覆盖现存的一个块,称为替换或驱逐这个块。
决定该替换哪个块是由缓存的替换策略来控制的。有的策略会替换掉最近最少使用的块。
- 缓存不命中的种类
-
如果第k层缓存是空的,那任何数据都不会命中。空的缓存被称为冷缓存,这种不命中称为强制性不命中或冷不命中。
-
在出现了冷不命中后,缓存需要按照放置策略来把下一层缓存中的数据放置在本层的缓存中,如果用随机放置成本太高,所以会强制一个或几个块,这种放置策略就引起了冲突不命中,
- 比如程序请求块0、然后块8、然后块0、然后块8,以此类推,在第k层的缓存中,请求了块0,接下来请求块8就必然不命中,去k + 1层读了块8再作为缓存后,接下来请求块0也必然不命中,以此类推。
-
程序通常是按照一系列阶段来运行,每个阶段访问缓存块的某个相对稳定不变的集合。例如,嵌套循环可能反复的访问同一个数组的元素。这个块的集合称为这个阶段的工作集。当工作集大小超过了缓存大小,缓存就出现了容量不命中。
- 缓存管理
每一层都需要某种逻辑来管理缓存,比如将缓存划分成块,在不同的层之间传送块,判定是命中还是不命中,并处理它们。管理缓存的逻辑可以使硬件、软件,或者两者结合。
例如,编译器管理寄存器文件,缓存层次结构的对高层。它决定当发生不命中时如何发射加载,以及确定哪个寄存器来存放数据。L1、L2和L3层的缓存完全是由内置在缓存中的硬件逻辑来管理,在一个有虚拟内存的系统中,DRAM主存作为存储在磁盘上的数据块的缓存,是由OS软件和CPU上的地址翻译硬件共同管理的。对于一个具有像AFS这样的分布式文件系统的机器来说,本地磁盘作为缓存,它是由运行在本地机器上的AFS客户端进行管理的。在大多数时候,缓存都是自动运行的,不需要程序采用特殊行动。
为什么会有缓存
- 利用时间局部性:由于时间局部性,同一数据对象可能会被多次使用。一旦一个数据对象在第一次不命中时被复制到缓存中,我们就会期望后面对该目标有一系列的访问命中。因为缓存比低一层的存储设备更快,对后面的命中的服务会比最开始的不命中快很多。
- 利用空间局部性:块通常包含有多个数据对象。由于空间局部性,我们会期望后面对该块中其他对象的访问能够补偿不命中后复制该块的花费。
高速缓存的结构和操作
本节主要讲CPU上的高速缓存系统,随着CPU(寄存器)和主存的速度差距增大,在主存和CPU之间设置SRAM作为主存的cache成为必须,而高速缓存的结构也是cache系统概念的样板。
通用的结构
-
设一个计算机系统其每个存储器地址有m位,形成了 M = 2 m M=2^m M=2m个不同的地址。
-
设有 S = 2 s S=2^s S=2s个组,每个组拥有E个高速缓存行,每个行由** B = 2 b B=2^b B=2b个字节**的数据块组成,有一位有效位标明行是否有效,t个标记位( t = m − ( b + s ) t=m-(b+s) t=m−(b+s))唯一标识高速缓存行里的块
-
每个高速缓存都可以用元组(S,E,B,m)表示,高速缓存的大小 C = S × E × B C=S \times E \times B C=S×E×B(不包含标记位与有效位)
-
高速缓存的查找结构是哈希(散列)查找。
-
当CPU试图从内存地址A处读取数据,其将地址A传递至高速缓存,若高速缓存存储有A处那个字的副本则从高速缓存读取
-
设目标数据的地址A长度为m位,这个地址被S和B分为三个字段,如上图图二所示。
-
组索引确定在哪一组
-
标记位标记在具体的行
-
块偏移量确定在数据块中A的具体位置
-
使用中间位作为组索引是为了让相邻的内存块映射到不同的缓存set里,是一种哈希偏移方式
直接映射高速缓存
组选择
行匹配
- 命中
- 不命中:
- 有效位==0
- 冲突不命中(见下)
字选择
最后一步确定需要的字在块中是从哪里开始的。块偏移位提供了所需要的字的第一个字节的偏移。我们把块看称一个字节的数组,而字节偏移是到这个数组的一个索引。
假定块大小为8位,
0b100=4,即从第4位开始存(假定存4位)
不命中时候的字替换
从K+1层(这里是内存)取出被请求的块存储在所指示组的任意一个行内,若组中都是有效的cache行就必须驱逐一个行
在直接映射高速缓存,每个组只包含有一行,替换策略非常简单:用新取出的行替换当前的行。
冲突不命中
直接映射高速缓存极易发生冲突不命中,即使在程序有较好的空间局部性、能够使得最近访问过的信息很可能在近期还会被再次访问的情况下也是如此。常见的状况就是“抖动”
例子:
组相联高速缓存
组相联高速缓存中的每个组都保存有多于一个的高速缓存行。
组相联高速缓存中的组选择
与直接映射高速缓存的组选择相同。
组相联高速缓存中的行匹配和字选择
组相联高速缓存中的行匹配必须检查多个行的标记位和有效位,以确定所请求的字是否在集合中。
高速缓存必须搜索组中的每一行,寻找一个有效的行,其标记与地址中的标记相匹配。如果高速缓存找到了这样一行,那么我们就命中,快便宜从这个块中选择一个字,和前面一样。
组相联高速缓存中不命中时的行替换
- 如果有一个空行,则选择该空行。
- 如果没有空行:
- 随机选择要替换的行。
- 最近最少使用(LRU)策略会替换最后一次访问时间最久远的那一行。
全相联高速缓存
即只有一个组,这个组包含了所有高速缓存行,好处是省去了组标记位,适合做小的缓存,比如虚拟内存系统中的TLB(翻译备用缓冲器)
高速缓存的写
关于怎么写?
- 存在多个数据副本:
- L1, L2, L3, 主存, 磁盘
- 在写命中时要做什么?
- 直写 (立即写入存储器)
- 写回 (推迟写入内存直到行要替换)
- 需要一个修改位 (和内存相同或不同的行)
- 写不命中时要做什么?
- 写分配 (加载到缓存,更新这个缓存行)
- 好处是更多的写遵循局部性
- 非写分配 (直接写到主存中,不加载到缓存中)
- 写分配 (加载到缓存,更新这个缓存行)
- 典型的
- 直写 + 非写分配
- 写回 + 写分配
现实中使用的高速缓存层级结构
高速缓存的性能影响
高速缓存性能指标
-
不命中率
- 一部分内存引用在缓存中没有找到 (不命中 / 访问), = 1–命中率
- 典型的数 (百分比):
- 3-10% L1
- 对于L2,可以相当小(e.g., < 1%),根据大小, 等等
-
命中时间
- 从高速缓存向处理器发送一行的时间
- 时间包括行是否在缓存中
- 典型的数:
- L1 4个时钟周期
- L2 10个时钟周期
- 从高速缓存向处理器发送一行的时间
-
不命中处罚
- 由于不命中需要额外的时间,通常主存为50-200周期(趋势: 增加!)
高速缓存友好的代码
-
让常见的情况运行得快
-
专注在核心函数和内循环上
-
使用内层循环的缓存不命中数量降到最低
-
反复引用变量是好的(时间局部性)
-
步长为1的 参考模式是好的(空间局部性)
-
关键思想: 局部性的定性概念是通过缓冲存储器的理解而量化了