CSAPP(4):存储器层次结构
存储器系统(memory system)是一个具有不同容量、成本和访问时间的存储设备的层次结构。
(一)存储设备的种类
(二)访问主存
读写操作由CPU上的总线接口电路发起。
根据上图中的数据流,对于读操作:
1.CPU将地址A放到系统总线上,然后I/O桥将信号传递给存储器总线;
2.主存感觉到存储器总线上的地址信号,从存储器总线读地址,从DRAM取出数据字,并将数据写到存储器总线。I/O桥将信号翻译为系统总线信号传递。
3.CPU感觉到系统总线上的数据,从总线上读数据。
对于写操作:
1.CPU将地址A放到系统总线上,然后I/O桥将信号传递给存储器总线;主存读出地址,等待数据到达;
2.CPU将数据字拷贝到系统总线
3.主存从存储器总线读出数据字,并存储。
这是CPU与主存之间数据传递的过程。如果更加细部地来看,对于主存来说,如何根据总线的信息读写数据?
(三)访问磁盘
当操作系统要执行IO操作时,操作系统会发送一个命令到磁盘控制器,让它读某个逻辑号。控制器上的一个固件执行快速表查找,将一个逻辑块号翻译成一个(盘面、磁道、扇区)三元组,读取数据放到控制器的一个缓冲区,然后拷贝到主存。
CPU使用一种称为存储器映射的技术来向IO设备发送命令。在使用存储器映射IO系统中,地址空间中有一块地址是为与IO设备通信保留的。这样的地址称为IO端口。
根据图示,磁盘读:
1.第一条指令是发送一个命令字,告诉磁盘发起一个读,同时还发送了其他参数(例如当读完成,是否中断CPU);第二条指令指明应当读的逻辑块号;第三条指令指明应该存储磁盘扇区内容的主存地址。发起磁盘读。
2.磁盘控制器收到命令,将逻辑块号翻译为扇区地址,读扇区内容,然后将这些内容直接传送到主存,不需要CPU干涉。这个过程叫做DMA传送(direct memory access)
3.DMA传送完成,磁盘控制器会给CPU发送一个中断信号通知CPU,这会导致CPU暂停它当前正在做的工作,跳转到一个操作系统例程。这个例程会记录下IO已经完成,然后将控制返回CPU被中断的地方。
(四)局部性
一个编写良好的程序会展现出良好的局部性。
时间局部性:在一个良好的时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来再被引用;
空间局部性:在一个良好的空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置。
评价程序局部性的简单原则:
1.重复引用同一个变量的程序有良好的时间局部性;
2.对于具有步长为k的引用模式,步长越小,空间局部性越好。
3.对于取指令来说,循环有好的时间和空间局部性。循环体越小,迭代次数越多,局部性越好。
(五)存储器结构
基于两个事实:1)不同存储技术的访问时间差很大,CPU和主存之间的速度差距在增大;2)良好的程序具有良好的局部性。人们想到一种组织存储器系统的方法:存储器层次结构。
存储器层次结构主要是依赖一个概念:缓存。
(六)高速缓存存储器
早期计算机系统的存储器层次结构只有三层:CPU寄存器、DRAM主存储器和磁盘存储。由于CPU和主存之间的差距逐渐增大,系统设计者被迫在插入L1高速缓存,而后又插入L2高速缓存。
多说一点:
C与M相差有多大?就是T/E那么大,因为缓存很小,所以T必然很大。
同样多的行,按S组织和按E组织有什么差别?按S组织,可以用地址来索引,类似于数组;按E组织,使用tag来匹配,类似于map。
根据E的不同,高速缓存被分为不同的类。E=1的高速缓存称为直接映射高速缓存(direct-mapped cache)。E>1的称为组相联高速缓存(set associative cache).
对于直接映射高速缓存,如果不命中的话,需要将从下一层中取出新块保存在缓存中。如果要替换的话,因为组中只有一行,所以直接替换即可。
由于只有组中只有一行,所以当程序访问大小为2的幂的数组时,经常发生冲突不命中。原因如下:
组相联高速缓存的基本原理类似于直接映射缓存。值得一提的是:不命中时的行替换。有随机替换策略、最不常用替换策略、最近最少使用替换策略。所有这些策略都需要额外的时间和硬件。但是,越往存储器层次结构下面走,一次不命中的开销越昂贵,所以用更好的替换策略更加值得。
上面只讲了读,如何写呢?
在高速缓存中更新了w的拷贝之后,怎么更新w在层次结构中紧接着低一层中的拷贝呢?
1.直写,就是立即将w的高速缓存块写回到紧接着的低一层中的拷贝。
2.写回,只有当替换算法要驱逐更新过的块时,才把它写到紧接着的低一层中。那么高速缓存必须为每一个高速缓存行维护一个额外的修改位,表明这个高速缓存是否被修改过
如何处理写不命中呢?
1.写分配,加载相应的下一层中的块到高速缓存中,然后更新这个高速缓存块。
2.非写分配,避开高速缓存,直接写到低一层。
直写高速缓存通常是非写分配的,写回高速缓存通常是写分配的。
(七)编写局部性好的代码
1.将你的注意力集中到内循环上,大部分的计算和存储器访问都发生在这里;
2.通过按照数据对象存储在存储器中搞得顺序,以步长为1的来读数据,从而使空间局部性最大;
3.一旦从存储器中读入了一个数据对象,就尽可能多的使用它,从而使得时间局部性最大