zoukankan      html  css  js  c++  java
  • 存储系统的结构与优化

    本文主要介绍存储系统的层次结构以及性能优化技术,包括Cache的性能优化、主存的性能优化和虚拟存储器的性能优化。

    存储器的层次结构

    本节主要关注存储器的层次结构是如何组成的,以及如何对性能进行评价。

    存储容量S=存储字长w×存储字数l×存储体个数m。

    存取速度需要通过访问时间TA和存取周期TM共同评估。其中,访问时间指接收命令到操作完成的时间,而存取周期指两次操作的最短间隔,即包括了存储器恢复(如DRAM刷新)的时间。

    传输速度通常用带宽BM来表示,即最大数据传输率,等于最小数据间隔的倒数。简单的存储器中,我们可以认为BM=m*w/TM。注意BM和TA可能没有必然的联系,尤其是对于支持突发传送的SDRAM,多体交叉MEM等。

    引入层次结构,是为了解决大容量、高速度、低价格之间的矛盾,基于程序访问的局部性原理,将近期常用数据放在小容量的高速存储器中。多种速度不同的存储器分层级联,协调工作。上层存储器中信息为下层中的副本,外部只访问最高层,而内部各级之间透明地传递信息,最终实现了平均价格接近最低级,而平均速度接近于最高级的目标,容量、速度、价格之间的矛盾基本消除。

    评价存储系统,主要关注其存储容量S、每位平均价格c、命中率H、平均访问时间TA。为实现层次结构,各级存储器之间要满足容量和速度上的数量级差异关系,同时保证有较高的命中率。

    层次结构的组织

    本节中我们更详细地探讨层次结构的层次数量以及层次内外的组织方式。

    存储系统通常划分为Cache、主存、辅存,通常以主存为中心。由于CPU只会直接提出对主存的访问并且按主存地址访存,因此我们说,存储器层次结构是以主存为中心的。

    Cache-主存层次致力于解决主存速度的问题。CPU按主存地址发出访问请求,辅助硬件Cache控制器(如果用虚拟地址的话其实也包括MMU)负责地址变换和层间管理。

    主存-辅存层次着重解决主存容量问题,也作为虚拟存储器的实现。引入虚拟存储器后,CPU按程序地址发出访问请求,经过MMU的地址变换后,以主存地址形式到达Cache。主存与辅存之间的层次管理由MMU和软件共同实现。

    层次管理单元需要负责实现各个层次之间的层次管理和地址变换。主存和辅存之间以虚拟地址访问,Cache和主存之间的访问可以是物理地址也可以是虚拟地址。使用虚拟地址访问Cache时,可以隐藏地址变换的时间开销,但由此带来了进程切换时清空困难、数据共享困难、I/O复杂等一系列问题。因此,通常来说,Cache和主存层次还是主要以物理地址来访问。

    在以下讨论中,若不作特殊强调,默认考虑以主存地址进行Cache-主存层次层间管理的情况。

    层间信息的交换由于可以采用突发传送等手段减少访问时间,需进行量化设计。为了消除瓶颈,要求各个层次的平均时间延迟大体相等。CPU和Cache通常以4B左右的字为单位,Cache和主存之间通常以32B左右的字为单位,而主存和辅存之间通常以4KB左右的页为单位。

    层次存储器的每个层次需要有支持地址变换、数据访问、数据写回操作的部件,主要解决映射规则、查找方法、替换算法、写策略问题。在解决这些问题的过程中,面向TA=命中时间+缺失率×缺失时间,每个因子上都能提出一些改进思路。

    Cache的基本工作原理

    Cache通过目录表来管理Cache-主存的映射。工作过程主要包括地址变换、数据访问、数据写回三个部分,所有工作全部由硬件完成。

    映射规则决定了一个主存块可以放到哪些Cache行中,对冲突率有很大影响。全相联性能最好,直接相联性能最差。考虑成本,实际使用组相联。

    查找方法决定了如何查找目标行,需要兼顾速度与成本。通常使用按地址索引,并行查找各个行的方式,即将块地址划分为标记和索引,由索引选择组,标记与各行比较。

    替换算法确定了如何从候选行中选处一个牺牲行,需要考虑命中率和成本。通常选择LRU算法,具体实现有堆栈和比较器两种。

    写策略确定了写操作的数据何时写到主存,需要考虑访问时间TA和对总线的占用成都。区分全写法和写回法以及是否按写缺失分配。对于连接总线的Cache通常采用写回,其余可以采用全写法。

    对Cache写主存的过程可以通过设置缓冲区来实现优化。对一个字设置写缓冲区,或者对一个块设置写回缓冲区,将数据先写入缓冲区后,用写提交代替写完成,而在Cache空闲或命中时再写回主存。

    Cache的缺失现象可以被分为三种:首次访问某个块时块不在Cache中,这样的缺失成为强制缺失;所需块不能全部调入,替换后又重新访问,这样的缺失称为容量缺失;多个块映射到同一组,替换后又重新访问,这样的缺失称为冲突缺失。

    Cache的性能优化

    提升Cache的性能主要有三个出发点:降低缺失率、减小缺失开销、减少命中时间。

    降低缺失率的方法

    增加Cache块大小。控制容量不变,随着块大小增加,缺失率先下降后上升。同时,这个最小值点,会随着Cache容量的增加而上升。之所以会出现缺失率随块大小增加到一定程度后继续增加而上升,是因为块大小增加导致了调块时间的增加,因此缺失时间增加。最终,块大小的选择,以平均访问时间TA最小为标准。总得来说,我们希望块尽可能大,但受到下级存储器的时延和带宽的牵制。

    提高相联度。实验表明,8路组相联和全相联的缺失率相当接近,而至少在容量不超过128KB时,直接映射大约等价于一半Cache大小的2路组相联映射。相联度的增加,导致缺失率下降,但命中时间上升。仍然根据TA最小为依据。具体而言,若命中时间等于一个时钟周期,那么n不应过度增大,以免影响时钟周期;否则,n可以较大,并通过其它手段去补偿命中时间。一般会选择n=8,时因为此时已经较好地利用了局部性,同时也接近同一时间片内执行的不同进程数。

    伪相联Cache。伪相联Cache基于直接映射,但每个行唯一对应一个兄弟行称为伪相联行(比如索引最高位取反)。首先查找直接映射对应的行,若缺失,查找伪相联行,若还缺失才访问下级。块也可以调入伪相联行。一个优化是在伪命中时交换两个行的内容。由于命中时间变幻莫测,伪相联Cache会导致流水线设计复杂化,顶多用在距离处理器较远的Cache如L2-Cache上。

    牺牲Cache。设置一个全相联的小Cache称为Victim,存放最近被替换出Cache的块。在Cache缺失时先查找Victim,如果命中则调入或者交换。由于采用了全相联,Victim的命中率较高,对平均缺失率降低有很大帮助。

    硬件预取。保证指令或数据在被访问之前,已经调入Cache。具体而言,在Cache缺失时,查找预取器中是否有当前块,发出调入当前块和预取下个块的请求。预取器命中时,将当前块送入Cache,并取消对当前块的调入请求。下个块的地址根据两次缺失地址的差值来选择。效果虽然显著,但预取未完成前可能会阻塞Cache请求,即使预取只会在Cache空闲时开始,新的请求也会到来。通常采用非阻塞Cache,将预取优先级降到最低而先处理调入,使得预取过程不会影响Cache访问。硬件预取在Core 2等处理器中得到应用。

    编译器控制预取。即软件预取,其存在问题是预取可能得不偿失。采用非阻塞Cache可以有所改善。相比硬件预取,其效率虽然较高,成本较低,但软件可移植性差。

    编译优化。通过优化软件减少指令、数据缺失率,具体包括代码重组和数据重组等方法。

    减少Cache的缺失开销的方法

    读缺失优先于写。在零等待写技术的基础上,读缺失优先于写的思想是,不论是被零等待写延后的写主存操作,还是因块替换引发的写回主存操作,在面临新的读主存操作时,都先进入缓冲区,等读操作完成后,再实施写。此时写缓冲区也变成了一个小Cache,因此工作过程改变为同时查找Cache和写缓冲区,在Cache缺失的情况下,若写缓冲区命中,则该块移到Cache中;若写缓冲区缺失,暂停当前所有写操作,立即将目标块调入。最终,读缺失时间等于读块的时间,而不再包含写字或写回的时间;写缺失时间,对全写法而言为0,对写回法而言,小于等于写块+读块的时间。(为什么不是仅仅读块,是考虑到缓冲区满了的情况吗?)命中时间几乎不受影响,因为Cache和缓冲区的查找是同时进行的。缺失率下降,因为写缓冲本身相当于一个Victim Cache。

    写缓冲合并。针对全写法Cache,我们希望合并写请求,隐藏写操作开销。写缓冲由若干个缓冲行组成,每行包含多个子项,每个子项由独立的有效位(类似于掩码)。对于写请求,如果地址能与已有的行匹配则合并请求,否则创建新请求。如果缓冲区满则直接阻塞。其对性能的提高作用,源于写缓冲区合并操作减少了主存的操作次数,同时减少了缓冲区的阻塞次数。

    请求字处理技术。在Cache块较大时,一个块的调入比一个字的调入要多花很多时间。我们希望,在块调入完成前,将数据送给CPU以完成操作,后续的调块继续进行。请求字处理技术具体分为两种方案。一种称为尽早重启方案,块仍然按序调入,只是在请求字到达时将它理机送给CPU,实现时需要计数器和选择器。另一种称为请求字优先方案,将先调入的请求字送给CPU,然后再调入块中的其他字,实现时需要专门的总线事务。其性能依赖于Cache块的大小以及块剩余部分的访问概率。

    非阻塞Cache。当前请求缺失时,一边调块,一边处理后续请求,尽可能将缺失时间隐藏。实现上,增设请求缓冲区,并在当前请求缺失时处理后续请求。通常只需要处理一个缺失即可,处理多重缺失情况下的命中意义不大,因为概率太小。可以应用在乱序执行CPU和采用了预取技术的Cache上。

    两级Cache。采用层次结构,L1小而快,L2容量较大,在不影响命中时间时减小缺失时间。事实上,在优化L2的缺失率时,无需顾忌L2的命中时间,当然显然更不会影响L1的命中时间。因此,L2可以具有较大的容量和较高的相联度,可以采用较大的块。注意这里TA下降的主要原因是命中时间和缺失率的下降,进一步增加级数减小的是缺失时间,意义不大。当下采用三级Cache主要是涉及到不同核的共享问题。

    减少Cache命中时间的方法

    采用小而简单的L1-Cache。容量小可以减小取目录项的时间,结构简单可以减小比较标记的时间,便于比较标记和访问数据并行,即使得比较器、译码器同时工作。

    虚拟索引Cache。我们希望让地址变换和取目录项同时进行。传统的虚拟Cache用虚拟地址来作为索引和标记的来源,但存在进程切换时需要清空、数据共享困难、I/O麻烦的问题。虚拟索引Cache从页内地址中取一块作为索引,用物理页号作为标记。当然这要求索引位数和块内地址位数的和不能超过页面大小,这也就意味着组相联的组数不能太大。此时该如何扩展Cache容量?提高相联度是有限制的,因为对缺失率优化并不明显,反而会增加命中时间。页着色保证物理页号和虚拟页号的最低m位相同,这样查找Cache的索引就可以多加上几位。页推测用硬件推测虚拟地址低m位映射的物理页号低m位,同样也可以将索引多加m位,当然在出现不匹配时,就可以用正确的索引来重新查找。

    路预测。预测下一次要访问组中的哪个行或块,需要对每个组设置一些预测位。比较预测行的标记,同时读该行缓存块,到缺失时再比较其它行的标记并修改预测位。此时,预测命中需要一个时钟周期,而失败则需要两个。但准确率是很高的,通常能达到80%-90%。

    访问流水化。前面我们已经实现了取目录项和读缓存块、比较标记和读出数据的并行,这里我们进一步将操作过程细化,使得各步骤流水进行。需要设置段间寄存器,同时使用双端口存储器结构。命中时间基本不变,而带宽成倍提高。本质上,就是通过设置缓冲器增加数据宽度的方法,在单次访问时间不变的情况下,使带宽得以提高。

    总结Cache优化的各种方法

    增加块大小可以降低缺失率但会增加缺失开销,虽然实现成本几乎为零但效果很优先。增加Cache容量可以降低缺失率,实现成本较小,广泛用于L2-Cache。提高相联度虽然可以降低缺失率,但以增加命中时间为代价,同时也有一定的硬件成本,在一定的限度内得到了广泛的使用。

    使用伪相联Cache和牺牲Cache都可以降低缺失率,但由于硬件成本,主要只在一些历史版本中使用。硬件预取和软件预取可以降低缺失率,但都对硬件提出了特殊的要求,需要非阻塞Cache配合,否则会导致得不偿失。编译优化是纯粹通过软件调整改善局部性来优化性能的,应用非常广泛。

    读失效优先于写隐藏了缺失时的部分写或写回开销,写缓冲Cache为全写法减小了访存次数,请求字处理技术隐藏了部分掉块时间,非阻塞Cache页隐藏了部分调块时间,两级Cache通过引入容量较大的L2-Cache减小了L1的缺失时间。这些方法都对缺失时间进行了较大的优化,同时没有对缺失率和命中时间造成反面的显著影响。

    将L1-Cache设计得小而简单可以在稍微牺牲缺失率的情况下减小命中时间,但毕竟影响到CPU周期长度所以还是应用很广泛。虚拟索引Cache通过隐藏地址变换的时间来降低命中时间,注意普通的虚拟Cache面临很多问题,所以我们采用虚拟索引物理标记的方法,并引入了一些优化方法。路预测引入了预测的思想,在P4中被使用。流水化Cache访问进一步通过重叠操作来减小命中时间,也得到了广泛应用。

    主存的性能优化

    主存的性能优化主要考虑并行处理。实际访存的需求通常时一次访问多个连续单元,且多次访问的地址连续。

    单体多字存储器增加存储字长,即存储芯片的I/O位数。此时虽然带宽成倍提高,但访问有效性差,功耗较高。比如在只需要写一个字时,需要先将多个字读到寄存器中,修改后再整体写回。这种方案可以适用于L1-L2缓存之间。

    多体交叉存储器采取交叉编址的方式,每隔T/m启动一个存储体,采用突发传输方式I/O。此时带宽成倍提高(近似),接口保持不变,因此对于各种存储器都适用。当然也可以并行访问。与单体多字的区别是,多体交叉存储器仍然是字级的存储器。

    独立存储体是交叉访问的一种改进。仍然采用交叉编址,但是每个存储体有独立的地址线和数据线。每个体可以独立进行操作,实现访问之间的重叠。控制器管理和调度多个请求,使得没有体冲突的请求并行。为进一步避免体冲突,可以采用循环交换优化、质数个数存储体等方案。独立存储体可以用作并行存储器,如Core i7的L3缓存有4个体。也可以用作双通道存储器,即不同的请求同时I/O。

    虚拟存储器的组织与优化

    虚拟存储器以透明方式为程序提供比主存空间大得多的存储空间。主存用作虚存的缓存,辅存用作虚存的宿主。

    虚存与主存之间的交换单位可能为段、页。关于层次管理,映射方式为全相联,映射表存放在主存中,查找时按虚地址索引,不使用标记,地址变换由加法或拼接得到,替换算法为伪LRU算法,写策略采用写回法。

    虚存与辅存之间的层次管理由文件头表和文件管理表共同完成。数据载入时,先分配页面,然后拷贝数据并修改页表项。数据交换的管理与Cache类似。

    虚存-主存地址变换由MMU实现,其它部分基本都由操作系统负责。

    主存命中时的访问时间包括表项地址计算、表项访存、地址变换、数据访存四部分。其中表项访存的部分可以通过增设快表来避免,一个小容量的页表缓冲器就可以由99.99%的命中率。表项地址计算的时间可以通过并行查快表和慢表来隐藏。TLB缺失通常用硬件处理来减小缺失时间。设置不同的页大小可以减少地址变换的次数,同样也可以减少命中时间。

    主存缺失时的访问时间包括请求排队、事件响应、事件处理的时间。为减少请求排队时间,可以将缺失作为异常事件而非中断。为了减少事件响应的事件,可以增设缺页向量寄存器以避免取向量时访存。

    主存的缺失率同样页影响着虚拟存储器的性能。降低主存缺失率可以通过增加主存容量、增加页大小、设置不同的页大小以及预取页调度等方法。

    虚拟存储器需要实现进程的保护和共享,将进程的地址空间划分为共享区域和私有区域。其它进程禁止访问私有区域,授权访问共享区域。保护主要包括两种类型:区域保护和访问保护。

    区域保护针对的是一个进程可以访问哪些区域的问题。

    映像表保护方式将进程的私有区域由进程私有的映像表管理,共享区域由系统公共的映像表管理。进程只允许访问自己的私有印象表和全局的系统映像表,在访问VM时,MMU会比较表项中的保护属性和进程的访问属性,这种方式不能实现分级保护。

    环状保护对每个进程分配级别换号,外层只能通过系统调用访问内层的共享空间。这种方式不能实现共享区域的点点保护。

    键式保护对进程的每个区域分配一个锁(存储键),OS将钥匙(访问键)传送给进程。进程只能访问配对成功的区域。

    访问保护针对的是一个进程可以以哪些操作访问一个区域。具体的类型包括读、写、执行等。映像表项中会含有允许的类型,访问时由MMU负责比较。

    一般来说,虚存会同时采用区域保护和换装保护,并在映像表项中存储多种保护信息。所有的保护操作由MMU在地址变换时实现。所谓保护模式,正是指采用了虚拟存储器、提供信息保护机制的CPU操作模式。

  • 相关阅读:
    javaweb基础(6)_servlet配置参数
    javaweb基础(5)_servlet原理
    读书笔记:java特种兵(上)
    基础算法(四):海量数据的处理方法
    基础算法(三)动态规划和贪心算法
    基础算法(二):堆排序,快速排序
    基本算法(一):插入排序,归并排序
    JVM基础和调优(六)
    JVM基础和调优(五)
    JVM基础和调优(四)
  • 原文地址:https://www.cnblogs.com/mollnn/p/14934453.html
Copyright © 2011-2022 走看看