必知必会-存储器层次结构
相信大家一定都用过各种存储技术,比如mysql,mongodb,redis,mq等,这些存储服务性能有非常大的区别,其中之一就是底层使用的存储设备不同。作为一个程序员,你需要理解存储器的层次结构,这样才能对程序的性能差别了然于心。今天带大家了解下计算机系统存储器的层次结构。
存储技术
首先了解下什么是存储器系统?
实质上就是一个具有不同容量、成本和访问时间的存储设备的层次结构。从快到慢依次为:CPU寄存器、高速缓存、主存、磁盘;
这里给大家介绍一组数据,让大家有一个更清晰的认识:
如果数据存储在CPU寄存器,需要0个时钟周期就能访问到,存储在高速缓存中需要4~75个时钟周期。如果存储在主存需要上百个周期,而如果存储在磁盘上,大约需要几千万个周期! — 出自 CSAPP
接下来一起深入了解下计算机系统涉及的几个存储设备:
随机访问存储器
随机访问存储器(RAM)分为静态RAM (SRAM) 和动态RAM(DRAM)。SRAM的速度更快,但也贵很多,一般不会超过几兆字节,通常用来做告诉缓存存储器。DRAM就是就是我们常说的主存。
访问主存
数据流是通过操作系统中的总线的共享电子电路在处理器和DRAM之间来来回回。每次CPU和主存之间的数据传送都是通过一系列复杂的步骤完成,这些步骤成为总线事务。读事务是将主存传送数据到CPU。写事务从CPU传送数据到主存。
总线是一组并行的导线,能携带地址、数据和控制信号。下图展示了CPU芯片是如何与主存DRAM连接的。
那么我们在加载数据和存储数据时,CPU和主存到底是怎样交互实现的呢?
首先来看一个基本指令,加载内存数据到CPU寄存器中:
movq A,%rax
将地址A的内容加载到寄存器%rax中,这个命令会使CPU芯片上称为总线接口(bus interface)的电路在总线上发起读事务,具体分为三个步骤:
- CPU将地址A放到系统总线上,I/O桥将信号传递到内存总线。详情看下下图a
- 主存感觉到内存总线上的地址信号,从内存总线读地址,从DRAM取出数据字,将其写到内存总线。I/O桥将内存总线信号翻译成系统总线信号,沿着系统总线传递到CPU总线接口。下图b
- CPU感觉到系统总线上的数据,从总线上读数据,并将数据复制到寄存器%rax。下图c
随机访问存储器,有个缺点是当断电后,DRAM和SRAM会丢失它们的信息,因此为易失性存储。
磁盘存储
磁盘是广为使用的保存大量数据的存储设备,目前我们家用电脑,动辄也都是1T的。它相比于基于RAM的只有几百或几千兆字节的存储器来说,虽然大但是读写性能差。时间为毫秒级,比DRAM读慢了10万倍,比SRAM慢了100万倍。
磁盘构造
磁盘是由盘片构成的。每个盘片有两面。表面覆盖着磁性记录材料。盘片中央是一个可以旋转的主轴(spindle),它使盘片可以以固定的速率旋转,通常是5400~15000转每分钟,磁盘通常包含多个盘片,密封在一个容器内。
如上图,我们可以看到,表面被划分为很多同心圆,称为磁道。磁道又被划分为很多扇区,每个扇区具有相同的数据位(通常512字节)。扇区之间有间隙隔开,用来存储标识扇区的格式化位。
多个盘片封装在一起到一个容器中,就是我们平时用的硬盘,称为磁盘驱动器。
磁盘容量
容量很好理解,就是磁盘一共可以存储的数据位。根据磁盘的构造,我们得出磁盘的容量由下面因素决定:
- 记录密度(recording density,位/英寸):磁道一英寸可以放入的位数。
- 磁道密度(track density,道/英寸):从中心主轴向外的半径上,一英寸可以有多少磁道。
- 面密度(areal density,位/平方英寸):记录密度与磁道密度的乘积。
通过上面的了解,增加磁盘容量其实就是增加面密度,近些年面密度每隔几年就会翻倍。下面大家可以看一下这个磁盘容量的计算公式:
磁盘容量=字节数/扇区 * 平均扇区数/磁道 * 磁道数/表面 * 表面数/盘片 * 盘片数/磁盘
结合一个例子方便各位理解:
假如我们有一个磁盘,有5个盘片,每个扇区512字节,没个面20000条磁道,每条磁道 300 个扇区,那么容量计算为:
磁盘容量 = 512 * 300 * 20000 * 2 * 5 = 30720000000字节=30.72G
磁盘操作
磁盘读写操作靠的是读写头来读写存储在磁性表面的位,它在传动臂的一端,通过这个传动臂沿着半径前后移动,从而读取不同的磁盘上数据,这个过程就成为寻道(seek)
通过上图可以清晰的了解到,在读取数据的时候,首先通过传动臂沿着半径将读写头移动到对应表面的磁道上,而表面一直在以固定的速率旋转,读取指定扇区的数据(磁盘是以扇区大小来读写数据)。因为对于数据访问来说,消耗时间主要集中在:寻道时间、旋转时间和传送时间。
- 寻道时间:即移动传动臂到包含目标扇区的磁道上所需的时间;
- 旋转时间:即寻道完成后,等待目标扇区的第一个位旋转到读写头下的时间;
- 传送时间:即扇区第一个位开始位于读写头下,到最后一个位所需的时间;
这里给出一个书上写的结论,访问一个磁盘扇区中512字节的时间主要是寻道时间和旋转延迟。也就是访问扇区中第一个字节花费很长时间,剩下的几乎不用时间。
这里大家可能有疑问,CPU是如何读取磁盘的数据到主存的,这就需要了解I/O总线。他们通过多种适配器连接到总线,而I/O总线连接了内存和CPU。如下图所示:
也就是I/O总线连接各种I/O设备、主存等。
固态硬盘
固态硬盘也就是俗称的SSD(Solid State Disk),是一种基于闪存的存储技术,目前常用的日常PC都用它来代替了磁盘,获取更快的速度。
SSD是内部由闪存构成,一个闪存由B个块的序列组成,每个块由P页组成。通常页的大小是512字节~4KB,块由32~128页组成,块的大小为16KB~512KB。
SSD的随机读比写快很多,是因为:
- 在写的时候,只有一页所属的整个块被擦除之后才能写。而擦除块需要较长时间,1ms级的,比读取高一个数量级。
- 如果写的页P已经有数据,那么这个块中所有带数据的页都必须被复制到一个新的已经擦除过的块,然后才能对页P写操作。
在大约进行100000次重复写之后,块会被磨损,不能在使用,所以这也是网上建议保存固态磁盘不要频繁格式化,作为系统盘的原因。
局部性
现在计算机频繁的使用基于SRAM的告诉缓存,为了弥补处理器-内存之间的差距,这种方法行之有效是因为局部性这个基本属性。
程序的局部性原理是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理又表现为:时间局部性和空间局部性。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。空间局部性是指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问。
上面我们介绍了内存和磁盘的读取逻辑,因此一旦某个数据被访问过,很快的时间内再次被访问,则会有缓存等手段,提高访问效率。
因此我们程序中应该尊村下列普遍方法:
- 重复引用相同变量的程序有良好的时间局部性;
- 总是顺序访问数据,跨越的步长越小,则程序的空间局部性越好。
- 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
比如一个for循环,这是平时经常使用到的场景。假设它访问一个同一个数组元素,那么这个数组就是当前阶段的访问工作集,在缓存够大的情况下,它是可以直接命中缓存的。
存储器层次结构
上面主要介绍了存储技术和计算机软件一些基本的和持久的属性:
- 存储技术:不同的存储技术的访问时间差异很大。速度较快的技术每字节的成本要比速度慢技术高,而且容量越小。CPU和主存之间的速度差距在增大;
- 计算机软件:一个便携良好的程序倾向于展示出良好的局部性。
而现在计算机系统中,硬件和软件这些基本属性互相补充的很完美,即高层从底层走,存储设备变得更慢、更便宜和更大,顶层的是CPU寄存器,CPU可以在一个时钟周期内访问他们,接下来是高速缓存SRAM、主存等 。
看上图所示,其中心思想就是:对于每个k,位于k层的更快更小的存储设备是作为位于k+1层更大更慢设备的缓存。
概括来说,基于缓存的存储器层次结构行之有效,因为较慢的存储设备比较快的设备更便宜,还因为程序倾向于展示局部性。
- 利用时间局部性:由于时间局部性,同一数据可能会被多次使用,在第一次使用缓存不命中后就被复制到缓存中,后面在访问时性能就比第一次快很多。
- 利用空间局部性:存储设备底层都有块的概念,作为基本的读取单位。通常块包含多个数据,由于空间局部性,后面对该块中其他对象的访问即命中缓存,弥补首次访问块复制的消耗;
总结
今天,这篇文章主要学习了计算机存储器的相关知识。
- 常用的存储技术,以及计算机是如何操作这些存储设备中的数据的。
- 讲解了程序中的局部性原理,时间局部性和空间局部性。方便大家写出更快的程序。
- 最后学习了整个计算机系统的存储器层次结构。存储系统其实就是一个多级缓存系统,上层的存储设备昂贵,容量小,价格贵,但是速度快,作为下一层设备的缓存。
阅读更多内容,请浏览我的个人小站: 爱上编程