第6章 存储器层次结构
关键词:存储器层次结构,磁盘容量,时间和空间局部性,随机访问存储器,命中和不命中
在我们对系统的研究种,我们依赖于一个简单的计算机系统模型,CPU执行指令,而存储器系统为CPU存放指令和数据。在我们简单的模型中,存储器是一个线性的字节数组,而CPU能够在一个常数时间内访问每个存储器位置。虽然迄今为止这都是一个有效的模型,但是它实际上没有反映现代系统实际工作的方式。
实际上,存储器系统是一个具有不同容量、成本和访问时间的存储设备的层次结构。CPU寄存器保存着最常用的数据。靠近CPU的小的、快速的高速缓存存储器(catche memory),作为存储在相对慢速的主存储器(main memory)中数据和指令子集的缓冲区域。主存暂时存放存储在较大的慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其它机器的磁盘或磁带上的数据的缓冲区域。
计算机系统中一个基本而持久的思想:如果你了解了系统是如何将数据在存储器层次结构中上上下下移动的,那么你可以编写你的应用程序,使得它们的数据项存储在层次较高的地方,在那里,CPU能更快的访问到它们。
这个思想围绕着计算机程序的一个称为局部性的基本属性。具有良好局部性的程序倾向于一次又一次地访问相同的数据项集合,或是倾向于访问临近的数据项集合。具有良好局部性的程序比局部性差的程序更多地倾向于从存储器层次结构中较高层次访问数据项,因此运行得更快。
6.1 存储技术
1 随机访问存储器(random-access memory)分为两类——静态的和动态的。静态RAM(SRAM)比动态RAM(DSAM)更快,但也贵得多。SRAM用来作为高速缓存存储器,既可以在CPU芯片上,也可以不在CPU芯片上。DRAM用来作为主存以及图像系统的帧缓冲区。
(1)静态RAM
SRAM将每个位存储在一个双稳态(bistable)存储器单元(cell)里。每个单元是用一个六晶体管电路来实现的。这个电路有这样一个属性,它可以无限期地保持在两个不同的电压配置(configuration)或状态中(state)之一。其它任何状态都是不稳定的——从那里开始,电路会迅速地转移到两个稳定状态中的一个。
由于SRAM有双稳特性,只要有电,它就可以永远地保持它的值。即使有干扰,例如电子噪音,来扰乱电压,当干扰消除时,电路就会恢复到稳定值。
(2)动态RAM
DRAM将每个位存储为对电容的充电。这个电容非常小。通常只有大约千万亿分之一法拉——也就是30*(10^(-15))法拉。与SRAM不同,DRAM存储器但愿对干扰非常敏感。当电容的电压被扰乱之后,它就永远不会恢复了。暴露在光纤下会导致电容电压改变。实际上,数码照相机和摄像机中的传感器本质上就是DRAM单元的阵列。
(3)访问主存
数据流通过称为总线(bus)的共享电路在处理器和DRAM主存之间来来回回。每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事务(bus transaction)。读事务(read transaction)从主存传送数据到CPU,写事务(write transaction)从CPU传送数据到主存。
总显示一组并行的导线,能携带地址、数据和控制信号。取决于总线设计,数据和地址信号可以共享同一组导线,也可以使用不同的。同时两个以上的设备也能共享同一个总线。控制线携带的信号会同步事务,并标识出当前正在被执行的事务的类型。
2 磁盘存储
(1)磁盘构造磁盘是由盘片(platter)构成的。每个盘片有两面,表面(surface)覆盖着磁性记录材料,盘片中间有一个可以旋转的主轴(spindle),它使得盘便宜固定的旋转苏联(rotational rate)旋转,通常是5400-15000RPM(revolution per minute,转每分钟)。磁盘通常包含一个或多个这样的盘片,且装在一个密封的容器内。
(2)磁盘容量
一个磁盘上可以记录的最大位数被称为它的最大容量,或者简称为容量。磁盘容量是由以下技术因素决定的:
记录密度(recording density)(位/英寸):磁道一英寸的段中可以放入的位数。
磁道密度(track density)(道/英寸):从盘片中心出发半径为一英寸的段内可以有的磁道数。
面密度(areal density)(位/平方英寸):记录密度与磁道密度的乘积。
现代大容量磁盘使用一种称为多区记录(multiple zone recording)的技术。在这种技术中,磁道集合被分成了不相交的子集合,称为记录区。每个区包含一组连续的磁道。一个区中的每个磁道都有相同数量的扇区,这个扇区的数量是由该区中最里面的磁道所包含的扇区数确定的。
(3)磁盘操作
磁盘以扇区大小的块来读写数据。对扇区的访问时间有三个主要组成部分:寻道时间、旋转时间和传送时间。
(4)存储技术趋势
不同的存储技术有不同的价格和性能折中;
不同存储技术的价格和性能属性以截然不同的速率变化着;
DRAM和磁盘访问时间滞后于CPU时钟周期。
6.2 局部性
一个编写良好的计算机程序倾向于展示出良好的局部性。
局部性通常有两种表现形式:时间局部性(temporal locality)和空间局部性(spatial locality)。在一个具有良好时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来再被多次引用。在一个具有良空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置。
程序员应该理解局部性原理,因为一般而言,有良好局部性的程序比局部性差的程序运行得更快。现代计算机系统的各个层次,从硬件到操作系统、到应用程序,它们的设计都利用了局部性。在硬件层,局部性原理允许计算机设计者通过引入高速缓存存储器的小而快速的存储器来保存最近被引用的指令和数据项,从而提高对主从的访问速度。在操作系统级,局部性原理允许系统使用住村作为虚拟地址空间最近被引用块的高速缓存。类似地,操作系统用驻村来缓存磁盘文件系统中最近被使用的磁盘块。局部性原理在应用程序的设计中也扮演着重要的角色。
1 对程序引用的局部性
如一维数组引用时候,步长越大,空间局部性下降。
如多维数组引用时候,如果先引用列,则会导致空间局部性下降。因为数组在存储器中是按行优先的存储方式。
2 取指令的局部性
因为程序指令是存放在存储器中的,而CPU必须取出(读出)这些指令,所以我们也能够评价程序关于取指令的局部性。例如,for循环体里的指令是按照连续的存储器顺序执行的,因此循环具有良好的空间局部性。因为循环体会被执行多谢,所以它也有很好的时间局部性。
代码区别于程序数据的一个重要属性是在运行时它是不会被修改的。当程序正在执行时,CPU从存储器中读出它的指令,CPU绝不会重写或修改这些指令。
3 局部性小结
量化评价程序中局部性的简单原则:
重复引用同一个变量的程序有良好的时间局部性。
对于具有步长为k的引用模式的程序,步长越小。空间局部性越好。具有步长为1额的应用模式的程序有很好的空间局部性。在存储器中以大步长跳来跳去的程序空间局部性会很差。
对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
6.3 存储器层次结构
1 计算机软件的一些基本和持久的属性
(1)不同存储设备的访问时间差异很大。速度较快的设备每字节的成本要比速度较慢的设备高,而且容容量较小。CPU和主存之间的速度差距在增大。
(2)一个编写良好的程序倾向于展示出良好的局部性。
存储器层次结构的中心思想是:对于每个k,位于k层的更快更小的存储设备作位于k+1层的更大更慢的存储设备的缓存。换句话说,层次结构中的每一层的缓存来自较低一次的数据对象。
2 缓存命中和缓存命不中
(1)缓存命中
数据总是以块大小为传送单元(transfer unit)在第k层和第k+1层之间来回拷贝的。虽然在层次结构中任何一对相邻的层次之间快大小时固定的,但是其它的层次对之间可以有不同的块大小。
当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d。如果d刚好在k层,那么就是缓存命中。如果第k层中没有缓存数据对象d,那么就是缓存命不中。当缓存不命中发生时,第k层的缓存从第k+1ceng 缓存中取出包含d的那个块,如果第k层的缓存已经满了的话,可能会覆盖现存的一个块。
覆盖一个现存的块的过程被称为替换(replacing)或驱逐(evicting)这个块。被驱逐的这个块也被称为牺牲块(victim block)。决定该替换哪个块是由缓存的替换策略来控制的。例如,一个具有随机替换策略的缓存会随机选择一个牺牲块。一个具有最近最少被使用(LRU)替换策略会选择那个最后被访问的时间距现在最远的块。
(2)缓存不命中的种类
强制性不命中:如果第k层是空的,,那么对任何对象的访问都会不命中。一个空的缓存有时被称为冷缓存(cold cache),此类命中被称为强制性不命中(compulsory miss)或冷不命中(cold miss)。冷不命中很重要,不会在稳定状态中出现,稳定状态指的就是在反复的存储器访问已经将缓存变暖了之后。
这种限制下的放置策略会引起一种不命中,称为冲突不命中(conflict miss)。
当工作集的大小超过缓存的大小,缓存会经历容量不命中(capacity miss)。换句话说,缓存就是太小了,不能处理这个工作集。
程序通常按照一系列阶段来运行的,每个阶段访问缓存块的某个相对稳定不变的集合。例如,一个嵌套的循环可能会反复地访问同一个数组的元素。这个块的集合被称为这个阶段的工作集(working set)。
3 存储器层次结构概念小结
存储器层次机构行之有效,是因为较慢的存储设备别较快的存储设备更便宜,还因为程序倾向于展示局部性:
利用时间局部性:根据时间局部性,同一数据对象可能会被多次使用,一旦一个数据对象在第一次不命中被拷贝到缓存中,我们就会期望后面对该目标有一系列的访问命中。因为缓存比低一层的存储设备更快,对后面的命中的服务会比最开始的不命中快很多。
利用空间局部性:块通常包含有多个数据对象。根据空间局部性,我们会期望后面对该块中其它对象的访问能够补偿不命中拷贝该块的花费。
6.4 高速缓存存储器
1 分类
通用的高速缓存存储器结构
直接映射高速缓存
组相连高速缓存
全相连高速缓存
高速缓存确定一个请求是否命中,然后抽取出请求的字的过程,分为三步:组选择、行匹配、字抽取。
指令高速缓存和统一的高速缓存
2 高速缓存参数的性能影响
(1)不命中率:在一个程序执行或程序的一部分执行期间,存储器引用不明中的比率。它是这样计算的:不命中数量/引用数量。
(2)命中率:命中的存储器引用率,它等于=1-不命中率。
(3)命中时间:从高速缓存传送一个字搭配CPU所需的时间,包括组选择、行确认和字选择的时间。对于L1高速缓存来说,命中时间典型的是1-2个时钟周期。
(4)不命中处罚。由于不命中所要的额外的时间。L1不命中需要从L2得到服务的处罚,典型是5-10个周期。L1不命中从主存得到服务的处罚,典型是25-100个周期。
优化高速缓存的成本和性能的折中是一项很精细的工作,它需要在现实的基础程序代码上进行大量的模拟。
(5)高度缓存大小的影响:一方面较大的高速缓存可能会提高命中率;另一方面,使得大存储器运行得更快总是要难一些的。结果较大的高速缓存可能会增加命中时间。对于芯片上的L1高速缓存来说这一点尤为重要,因为它的命中时间必须为一个时钟周期。
(6)块大小的影响:有利有弊。较大的块能利用程序中可能存在的空间局部性帮助提高命中率,不过对于给定的高速缓存大小,块越大意味着高速缓存行数越少,这会损害时间局部性比空间局部性更好的程序中的命中率。较大的块不命中处罚也有负面影响,因为块越大,传送时间就越长。现代系统通常会折中,使高速缓存块包含4-8个字。
(7)相连度的影响:较高的相连度得优点是降低了高速缓存由于冲突不命中出现抖动的可能性。不过较高的相连度会造成较高的成本。较高的相连度实现起来很昂贵,而且很难使之速度变快。每一行需要更多的标记位,每一行需要额外的LRU状态位和额外的控制逻辑。较高的相连度会增加命中时间,因为复杂性增加了,另外,还会增加不命中处罚,因为选择牺牲行的复杂性也增加了。相连度的选择最终变成了命中时间和不命中处罚之间的折中。
(8)写策略的影响:直写高速缓存比较容易实现,而且能使用写缓冲区(write buffer),它独立于高速缓存,用来更新存储器。
3 高速缓存行、组和块的区别
块:是一个固定大小的信息报,在高速缓存和主存(或下一层高速缓存)之间来回传送。
行:是高速缓存中存储块以及其它信息(例如有效位和标记位)的容器。
组:是一个或多个行的集合。直接映射高速缓存中的组只由一行组成。组相连和全相连高速缓存中的组是由多个行组成的。
6.5 编写高速缓存友好(cache friendly)的代码
1 让最常见的情况运行得快
程序通常把大部分时间花在少量的核心函数上,而这些函数通常把大部分时间都花在了少量循环上。所以要把注意力集中在核心函数里的循环上,而忽略其它部分。
2 在每个循环内部使缓存不命中数量最小
(1)对局部变量的引用是好的,因为编译器能够将它们缓存在寄存器堆中(时间局部性)
(2)对步长为1的引用模式是好的,因为存储器层次结构中所有层次上的缓存都是将数据存储位连续的块(空间局部性)。
在对多维数组进行操作的程序中,空间局部性尤其重要。
6.6 综合:高速缓存对程序性能的影响
1 存储器山
2 重新排列循环以提高空间局部性
3 使用分块来提高时间局部性
6.7 在程序中利用局部性
1 将你的注意力集中在内部循环上,大部分计算核存储器访问都发生在这里
2 通过按照数据对象存储在存储器的顺序来读取数据,从而使得你程序中的空间局部性最大。
3 一旦从存储器中读入了一个数据,就尽可能地多使用它,从而使得你程序中的时间局部性最大。
4 记住不命中率只是确定你代码性能的一个因素(虽然是重要的)。存储器访问数量也扮演着重要角色,有时候需要折中。
参考文献
布赖恩特, O'Hallaron D, et al. 深入理解计算机系统[M]. 中国电力出版社, 2004.