《深入理解计算机系统》(四)

编译器优化对用户来说应该是不可见的,当程序员用优化选项编译代码时,代码的行为应该和不带优化编译得到的代码行为完全一样,除了它应该运行的更快一点,这样的要求使得编译器不能使用某些类型的优化。

存储器别名使用:编译器必须假设不同的指针可能会指向存储器中同一个位置,这造成了一个主要的妨碍优化的因素。

大多数编译器不会试图判断一个函数是否没有副作用,因此任意函数都可能是优化的候选者。相反,编译器会假设最糟的情况,并保持所有的函数调用不变。

表示程序性能:我们需要一种方法来表示程序性能,它能指导我们改进代码。对许多程序都很有用的度量标准是每元素的周期数(CPE)。这种度量标准帮助我们在更详细的级别上理解迭代程序的循环性能。同时,这样的度量标准对执行重复计算的程序来说也是很适当的。

处理器活动的顺序是由时钟控制的,时钟提供了某个频率的规律信号,要么用兆赫兹(MHz,即百万周每秒)来表示,要么用千兆赫兹(GHz)来表示。

程序示例:为了说明一个抽象的程序时如何被系统地转换成更有效的代码的。

默认地,编译器产生适合于用符号调试器一步一步调试的代码。因为目的是使目标代码尽可能类似于源代码中表明的计算,所以几乎没有进行什么优化。养成进行这一级优化的习惯是很好的。

注意:除了浮点数乘法外,对于各种数据类型和不同运算的时间基本上相等的。浮点数乘法有很高的时钟周期数是由于我们基准程序数据中的异常。

减少过程调用:过程调用会带来相当大的开销,而且妨碍大多数形式的程序优化。

例如:程序循环里没有函数调用,它没有用函数调用来获取每个向量元素,而是直接访问数据,一个纯粹主义者可能会说这种变换严重地损坏了程序地模块性。通常,向量抽象数据类型的使用者甚至不应该需要知道向量的内容是作为数组来存储的。而不是作为诸如链表之类的某种其他数据结构来存储的。这种变换的有点是可以提高速度,对于性能至关重要的程序来说,为了速度,经常必须要损害一些模块性和抽象性。如果以后需要修改代码,添加一些对所采用的变换进行记录的文档是很明智的。

理解现代处理器:到目前为止,我们运用的优化都不依赖于目标机器的任何特性,这些优化知识简单地降低了过程调用的开销,以及消除了一些重大的“妨碍优化的因素”,这些因素会给优化编译器造成困难。随着我们试图进一步提高性能,我们必须考虑这样的优化,它们更多地利用处理器执行指令的方式和某些处理器进行调整

,尽管如此,我们还是能够运行一些基本的优化,但很大一类处理器上产生整体的性能提高。

由于大量的晶体管可以被集成到一块芯片上,现在微处理器采用了复杂的硬件,试图使程序性能最大化。一个后果就是处理器的实际操作与观察汇编语言程序得到的概念大相径庭。在汇编代码级,看上去似乎是一次执行一个指令,每条指令都包括从寄存器或存储器取值,执行一个操作,并把结果返回到一个寄存器或存储器位置。在实际的处理器中,是同时对多条指令求值的。

ICU从指令高速缓存中读取指令,指令高速缓存是一个特殊的高速缓存存储器,它包含最近访问的指令。通常,ICU会在当前正在执行的指令很早之前取指,所以它有足够的时间对指令解码,并把操作发送到UE,不过,当程序遇到分支时,程序有两个可能前进的方向,一种可能会选择分支,控制被传递到分支目标,另一种是不选择分支,控制被传递到指令序列的下一条指令。

现代处理器采用了一种称为分支预测的技术,在这种技术中处理器会预测是否选择分支,同时还预测分支的目标地址。。

使用一种称为投机执行的技术,处理器会开始取出它预测的分支处理的指令并对指令解码,甚至于在它确定分支预测是否正确之前就开始执行这些操作。。如果过后它确定分支预测错误,它会将状态重新设置到分支点的状态,并开始取出和执行另一个方向上的指令。一种更加异乎寻常的技术是开始取出和执行两个可能方向上的指令,随后再抛弃掉不正确方向上的结果。至今都不认为这种方法的成本效率是值得的。

指令解码逻辑接收实际的程序指令,并将它们转换成一组基本操作。每个操作都完成某个简单的计算任务。

EU接收来自指令读取单元的操作。通常,它会每个时钟周期接收若干个操作。这些操作会被分配到一组功能单元中,它们会执行实际的操作。这些功能单元是专门用来处理特定类型的操作。

如:整数/分支:执行简单的整数操作(加法、测试、比较、逻辑)。还处理分支

通用整数:可以处理所有的整数操作包括乘法和除法。

浮点加法:处理简单的浮点操作(加法、格式转换)

浮点乘法/除法:处理浮点乘法除法。更复杂的浮点指令,例如超越函数,会被转换成操作的序列。

加载:处理从存储器读数据到处理器的操作,这个功能单元有一个加法器来执行地址计算。

存储:处理从处理器到存储器的写操作。这个功能单元有一个加法器来执行地址计算。

使用投机执行技术,对操作求值,但是最终结果不会存放在程序寄存器或数据存储器中,直到处理器能确定应该实际执行这些指令。分支操作被送到EU,不是确定分支该往哪里去,而是确定分支预测是否正确。如果预测错误,EU会丢弃分支点之后计算出来的结果。它还会发信号给分支单元,说预测是错误的,并指出正确的分支目的。分支单元开始读取新位置的指令,这样的预测错误会导致很大的性能开销。

指令解码时,关于指令的信息被放置在一个先进先出的队列中,这个信息会一直保持在队列中,直到两个结果中的一个发生。首先一旦指令的操作完成了,而所有导致这条指令的分支点也都被确认为预测正确,那么这条指令就可以退役了,所有对程序寄存器的更新都可以被实际执行了,另一方面,一旦预测错误,这条指令会被清空,丢弃所有计算出来的值。

任何对程序状态的更新都只会在指令退役时才会发生,只有在处理器能够确信导致这条指令的所有分支都预测正确了。才能这样做。

为了加速一条指令到另一条指令的结果传送,许多此类信息是在执行单元之间交换的。

最常见的控制操作数在执行单元间传送的机制称为寄存器重命名。

每个操作都是有两个周期计数值来刻画的:一个是执行时间,它指明功能单元完成操作所需要的总周期数;另一个是发射时间,它指明连续的、独立操作之间的周期数。

通常处理器性能是受三类约束限制的。第一、程序中的数据相关性迫使一些操作延迟直到它们的操作数被计算出来。第二、资源约束限制了在任意给定时刻能够执行多收个操作。第三、分支预测逻辑的成功限制了处理器能够在指令流中超前工作以保持执行单元繁忙的程度,每次预测错误。处理器从正确的位置重新开始都会引起很大的延误。

循环展开的缺点是它增加了生成的目标代码的数量。

编译器可以很容易地执行循环展开,只要优化级别设置的足够高,许多编译器都能例行公事地做到这一点。

转换到指针代码:在进行想下一步之前,我们应该再尝试一种有时能改进程序性能的转换,但这是以程序的可读性为代价的。C的一个独特的特性是能够对任意的程序对象创建和指针。实际上指针运算与数组引用有很紧密的联系。

根据我们的经验。指针和数组代码的相对性能依赖于机器、编译器,甚至于某个特殊的过程。我们已经看过编译器,他们对数组代码应用非常高级的优化,而对指针代码只应用最小限度的优化。为了可读性的缘故,通常数组代码更可取一些。

提高并行性:我们的程序是受功能单元的执行时间限制的。不过如果处理器的几个功能单元是流水线化的,这意味着它们可以在前一个操作完成之前开始一个新的操作。我们的代码不能利用这种能力,即使是使用循环展开也不能,知识因为我们将积累值放在一个单独的变量X中,直到前面的计算完成之前,我们都不能计算X的新值。因此处理器会暂停,等待开始新的操作,直到当前操作完成。

循环分割:对于一个可结合和可交换的合并操作来说,比如说整数加法或减法,我们可以通过将一组合并操作分割成两个或更多的部分,并在最后合并结果来提高性能。

寄存器溢出:循环并行性,如果我们有并行度超过了可用的寄存器数量,编译器会溢出,将某些临时值存放到栈中。一旦出现这种情况,性能会急剧下降。

对于整数数据类型的情况,总共只有八个整数寄存器可用,其中两个指向栈中的区域,一个存放指针data,还有一个要存放停止位置dend,剩下四个整数寄存器可以用来存放积累的值

八个整数和八个浮点寄存器的限制是IA指令集的不幸产物。

通过检查汇编代码就能发现溢出的发生。

使用浮点数据时,我们希望将所有的局部变量都放在浮点寄存器栈中,我们还需要保持栈顶可以用于从存储器加载数据,这限制了并行度小于或等于7.

对于并行的限制:对于我们的基准程序,主要的性能限制是由于功能单元的能力。整数乘法器和浮点加法器只能每个时钟周期发起一条新操作,加上对加载单元的类似限制,将这些情况的CPE限制在了1.0。浮点乘法器只能每两个时钟周期发起一条新的操作,将CPE限制在了2.0

变换平台:虽然我们是在一个特殊的机器和编译器环境中讲述我们的优化策略的,但是通用的原则也适用于其他机器的编译器,当然最优的策略可能与机器相关的。

分支预测:

投机执行:直到最近,支持投机执行所需要的而技术都被认为是开销太大,对于除了最高级的超级计算机以外的所有机器来说都是异乎寻常的。集成电路的技术使得可以再一块芯片上放置如此之多的电路,以至于有些电路可以专门用来支持分支,到目前,台式机或服务器中几乎每个处理器都支持投机执行。

在优化我们的合并过程中,我们没有看到循环结构对性能的任何限制,也就是说,看上去对性能唯一的限制因素是由于功能单元对于这个过程处理,处理器通常能够预测循环结尾处的分支的方向。实际上,如果处理器总是预测会选择分支,那么除了对最后一次迭代以外,它都是对的。

预测分支的方法:预测任意到较低地址的分支都会被选择,而任意到较高地址的分支则不会,到较低地址的分支是用来关闭循环的,因为循环通常会执行多次。另一方面,向前分支时用于条件计算的。前向不选择后向选择的启发方式占65%是正确的。

许多和数据相关的分支时不能预测的。

不幸的是。C程序员对改进一个程序的分支性能是无能为力的,除了意识到数据相关的分支会引起性能上很高的花费,除此之外,程序员对编译器产生的详细的分支结构几乎没有什么控制,很难使分支更容易预测一些。最终我们必须依靠两种因素的结合:一、编译器生成好的代码,尽量减少条件分支的使用。,另一个处理器有效地分支预测,降低分支预测错误的数量。

理解存储器性能:到目前为止我们写的所有代码,以及我们运行的所有测试,对存储器的需求都相对减少。所有的现代处理器都包含一个或多个高速缓冲存储器,以提供对这样少量的存储器的快速访问。

加载操作能利用流水线化,每个时钟周期开始新的加载操作,数据加载相对较长的而时间对程序性能没有任何负面影响。

使用加载操作从一个存储器位置读数据到一个寄存器中来与存储器交换。与之相对应的存储操作将一个存储器值写到存储器。

与加载操作一样,在大多数情况中,存储操作能够在完全流水线化的模式中工作,每个周期开始一条新的操作。

到目前为止我们已经考虑过的其他操作不同,存储操作并不影响任何寄存器值。因此就其本性来说,一系列存储操作一定是相互独立的,实际上,只有一条加载操作是受一条存储操作结果影响的,因为只有一条加载操作能从由存储操作写的那个存储器位置读返回。

存储单元包含一个存储缓冲区,它包含已经被发射到存储单元而又还没有完成存储操作的地址和数据,这里的完成包括更新数据告诉缓存。提供这样一个缓冲区,使得一系列存储操作不必等待每个操作更新高速缓存就能够执行。当一条加载操作发生时,它必须检查存储缓冲区的条目,看有没有地址相匹配。如果有地址相匹配,它就取出相应的数据条目作为加载操作的结果。

现实生活,性能提高技术:

1、高级设计,为手边的问题选择适当的算法和数据结构,要特别警觉,避免使用会渐进地产生糟糕性能的算法或编码技术。

2、基本编码原则。避免限制优化的因素,这样编译器就能产生高效代码。

。。消除连续的函数调用。在可能时将计算移到循环外,考虑有选择的妥协程序的模块性以获得更大效率。

。。消除不必要的存储器引用。引入临时变量来保存中间结果,只有在最后的值计算出来时,才能将结果放到数组或全局变量中。

3、低级优化。

。。尝试各种与数组代码相对的指针形式。

。。通过开展通过展开循环降低循环开销。

。。通过诸如迭代分割之类的技术,找到使用流水线化的功能单元的方法。

忠告:避免花费精力在令人误解的结果上,一项有用的技术是,在优化代码时使用检查代码来测试代码的每个版本,以确保在这一过程中没有引入错误。检查代码将一系列测试应用到程序上,确保它得到期望的结果,当引入新的变量,改变循环边界,以及使代码整体更复杂时很容易出错,此外,注意性能上任何不同寻常的或出乎意料的变化是很重要的。

确认和消除性能瓶颈:到此刻为止,我们只考虑了优化小的程序,在小程序中,需要优化的地方很清楚,在处理大程序时,我们很难知道应该优化什么.

程序剖析(在程序执行时收集性能数据的分析工具):包括运行程序的这样一个版本,其中插入了工具代码,以确定程序的各个部分需要多少时间。确认出程序中我们需要集中注意力优化的部分是很有用的。剖析的一个有利之处在于可以一边在实现的基准数据上运行实际的程序,一边进行剖析。

Amdahl定律:Gene  Amdahl(计算机领域的先驱之一)主要思想是当我们加快系统的一个部分的速度时,对系统整体性能的影响依赖于这个部分有多重要和速度提高了多少。

小结:虽然关于代码优化的大多数论述都描述了编译器是如何生成高效代码的,但是应用程序员有很多方法来协助编译器完成这项任务。没有任何编译器能用一个好的算法或数据结构代替低效率的算法或数据结构,因此程序设计的这方面仍然应该是程序员主要关心的同样,程序员必须对消除这些妨碍优化的因素负主要的责任。

随着我们对优化的深入,研究汇编代码以及试着理解机器是如何执行计算的变的重要起来。对于现代、乱序处理器上的执行,分析程序时如何在无限处理资源但是功能单元的执行时间和发射时间与目标处理器相符的机器上执行的。

包含条件分支或与存储器系统复杂交互的程序,比我们首先考虑的简单循环程序更加难以分析和优化。基本策略是使循环更容易预测,并试着减少存储和加载操作之间的相互影响。

当处理人形程序时,我们的注意力集中在在耗时的部分变得很重要,代码剖析程序和相关的工具能帮助我们系统地评价和改进程序性能。

这些工具可以在过程级分解执行时间,测量程序每个基本块的性能。。

                                                     第六章、存储器层次结构

CPU执行指令,而存储器系统为CPU存放指令和数据。存储器系统时一个线性的字节数组,而CPU能够在一个常数时间内访问每个存储器位置。。

实际上,存储器系统时一个具有不同容量、成本和访问时间的存储设备的层次结构。CPU 寄存器保存着最常用的数据。靠近CPU的小的、快速的高速缓存存储器作为存储在相对慢速的主存储器(主存)中数据和指令子集的缓冲区域。主存暂时存放存储在较大的慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其他机器的磁盘或磁盘上的数据缓冲区域。

存储器层次结构是可行的,这是因为与下一个更低层次的存储设备相比来说,一个编写良好的程序倾向于更频繁地访问某一层次上的存储设备。所以,下一层的存储设备可以更慢速一点,也因此更大,每个位更便宜。整体效果是一个大的存储器池,其成本与层次结构底层最便宜的存储设备相当,但是却以接近于层次结构顶部存储设备的高速率向程序提供数据。

作为一个程序员需要理解存储器层次结构,因为它对你的程序性能有巨大的影响。如果你的程序需要的数据是存储在CPU寄存器中的,那么在执行期间,在零个周期内就能访问到它们。如果存储在高速缓存中,需要1~10个周期,如果存储在主存中,需要50~100个周期,而如果存储在磁盘上,需要大约20 000 000个周期。

理解系统是如何将数据在存储器层次结构中上上下下移动的,那么就可以编写应用程序,使得它们的数据项存储在层次结构中较高的地方,在哪里CPU能更快地访问到它们。

这个思想围绕着计算机程序的称为局部性的基本属性。具有良好局部性的程序比局部性差的程序更多地倾向于从存储器层次结构中较高层次处访问数据项,因此运行的更快。

计算机技术的成功很大程度上源自于存储技术的巨大进步。

随机访问存储器(RAM)分为两类——静态的和动态的。静态RAM(SRAM)比动态RAM(DRAM)更快,但也贵的多。SRAM用来作为用来作为高速缓存存储器,既可以在CPU芯片上,也可以不在CPU芯片上。DRAM用来作为主存以及图形系统的帧缓冲区。例如,一个桌面系统的SRAM不会超过几兆字节,但是DRAM却有几百或几千兆字节。

易失性存储:如果断电,DRAM和SRAM会丢失他们的信息

非易失性存储:即使是关电,DRAM和SRAM仍然保存着他们的信息。

虽然ROM中有的类型可以读可以写,但是它们整体上都被称为ROM(read-only  memery只读存储器)

ROM是以它们能够被重编程(写)的次数和对他们进行重编程所用的机制来区分的。

访问主存:数据流通过称为总线(bus)的共享电路在处理器和DRAM主存之间来来回回,每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事物

读事物:从主存传送数据到CPU

写事物:从CPU传送事物到主存

总线是一组并行的导线,能携带地址、数据和控制信号。取决于总线设计。数据和地址信号可以共享同一组导线。也可以使用不同的。同时,两个以上的设备也能共享同一个总线。

控制线携带信号会同步事物,并表示出当前正在被执行的事物的类型。

磁盘存储:磁盘应用于大量数据的存储设备,而基于RAM的存储器很小,不过从磁盘上读取信息需要几毫秒,比从DRAM读慢了10万倍,比从SRAM读慢了100万倍。

磁盘构造:磁盘是由一个或多个叠放在一起的盘片构成的,每个盘片有两面。表面覆盖着磁性记录材料,盘片中间有一个可以旋转的主轴,它使得盘片以固定的旋转速率旋转。磁盘通常包含一个或多个这儿样的磁盘,且装在一个密封的容器内。

每个表面是由一组称为磁道(track)的同心圆组成的,且每个磁道被划分为一组扇区(sector)。每个扇区包含相等数量的数据位,这些数据编码在扇区上的磁性材料中,扇区之间有一些间隙分隔开,这些间隙中不存储数据位,间隙存储用来标识扇区的格式化位。

磁盘驱动器又简称为磁盘。

磁盘制造商通常用术语柱面来描述多个盘片驱动器的构造,这里柱面是所有盘片表面到中心主轴的距离相等的磁道的集合。

磁盘容量:一个磁盘上可以记录的最大为数被称为它的最大容量,或者简称为容量。

决定磁盘容量的技术因素:记录密度、磁道密度、面密度。

磁盘操作:磁盘用连接到一个传动臂的读/写头来读写存储在磁盘表面的位。

磁盘以扇区大小的块来读写数据。

对扇区的访问时间有三个主要组成部分:

1、寻道时间:为了读取某个目标扇区的内容,传动臂首先将读/写头定位到包含目标扇区的磁道上。

2、旋转时间:一旦读/写头定位到了期望的磁道,驱动器等待目标扇区的第一个位旋转到读/写头下。

3、传送时间:当目标扇区的第一个位位于读/写头下时,驱动器就可以开始读或者写该扇区的内容了。

虽然I/O总线比系统总线和存储器总线慢,但是它可以容纳种类繁多的第三方I/O设备例如:USB、图形卡(或适配器)、磁盘控制器包含硬件和软件逻辑。

网络适配器可以通过将适配器插入到主板上空的扩展槽中,从而连接到I/O总线,这些插槽提供了到总线的直接电路连接。

CPU使用一种称为存储器映射I/O的技术来向I/O设备发射命令。

存储技术的几个重要思想:

1、不同的存储技术有不同的价格和性能折中。

2、不同存储器技术的价格和性能属性以截然不同的速率变化着。

3、DRAM和磁盘访问时间滞后于CPU时钟周期时间(CPU的速度增长的比较快)

局部性:一个编写良好的计算机程序倾向于展示出良好的局部性,这种倾向性被称为局部原理,是一个持久的概念,对硬件和软件系统的设计有着极大的影响。

局部性的两种形式:时间局部性和空间局部性

程序员应该理解局部性原理

一般有梁海局部性的程序比局部性差的程序运行的更快。

取指令的局部性:因为程序指令是存放在存储器中的,CPU必须取出(读出)这些指令,所以我们也能够评价一个程序关于取指令的局部性。

代码区别于程序数据的一个重要属性是在运行时它是不能被修改的。。当程序正在运行时,CPU只从存储器中读取它的指令,CPU绝不会重写或修改这些指令。

程序中局部性的简单原则:

1、重复引用同一个变量的程序有良好的时间局部性。

2、对于具有步长为k的引用模式的程序,步长越小,空间局限性越好。

3、对于取指令来说,循环有好的时间和空间局部性,循环体越小,循环迭代次数越多,局部性越好。

高速缓存:是一个小而快速的存储设备,它作为存储在更大、也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程被称为缓存。

缓存命中:当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d.如果d刚好缓存在第k层中,那么就是我们说的缓存命中。

缓存不命中;如果第k层中没有缓存数据对象d,那么,,,,,

覆盖一个现存的块的过程被称为替换或驱逐这个块。被驱逐的这个块有十也称为牺牲块。决定该替换哪个块是由缓存的替换策略来控制的。

存储器层次结构的本质:每一层存储设备都是较低一层的缓存,在每一层上,某种形式的逻辑必须管理缓存。

某个东西要将缓存划分成块,在不同的层之间传递块,判定是命中还是不命中,并处理它们。

存储器层次结构行之有效,是因为较慢的存储设备比较快的存储设备更便宜,还因为程序倾向于展示局部性。

早期计算机系统的存储器层次结构只有三层:CPU寄存器、主DRAM存储器和磁盘存储设备。不过由于CPU和主存之间逐渐增大的差距,系统设计者被迫在CPU寄存器文件和主存之间插入了一个小的SRAM存储器称为L1高速缓存,和L2高速缓存。

如果CPU请求的字不再组的任何一行中,那么就是不命中,,高速缓存必须从存储器中取出包含这个字的块,一旦高速缓存取出了这个块,该替换哪一行呢?如果有一个空行那就是很好的候选,如果没有空行就必须选一个替换。

程序员很难在他们代码中利用高速缓存替换策略。最简单的替换策略是随即选择要替换的行。

一个全相联高速缓存是由一个包含所有高速缓存行的组组成的。

因为高速缓存电路必须并行地搜索许多相匹配的标记,构造一个又大又快的相联高速缓存很困难而且很昂贵,因此,全相联高速缓存只适合做小的高速缓存。

高速缓存关于写读的操作非常简单,写的情况要复杂一些。

如何处理写不命中:

1、写分配:加载相应的存储器块到高速缓存中,然后更新这个高速缓存块。

2、非写分配:避开高速缓存,直接把这个字写到存储器中。

为写操作优化高速缓存是一个细致而困难的问题。

到目前为止,我们认为高速缓存只保存程序数据,实际上,高速缓存既保存数据也保存指令。

只保存指令的高速缓存称为i-cache 只保存程序数据的高速缓存称为 d-cache。既保存指令又保存数据的高速缓存称为同意的高速缓存。

高速缓存大小的影响:较大的高速缓存可能会提高命中率,使得大存储器运行的更快总是要难一些,结果,较大高速缓存可能会增加命中时间。对于芯片上的L1高速缓存来说这一点尤为重要。因为它的命中时间必须为一个时钟周期。

块大小的影响:较大的块能利用程序中可能存在的空间局部性,帮助提高命中率,不过对于给定的高速缓存大小,块越大就意味着高速缓存行数越少,这回损害时间局部性比空间局部性更好的程序中的命中率。较大的块对不命中处罚也有负面影响,因为块越大传送时间就越长。

相联度的影响:这里的问题是参数E(每个组中高速缓存行数)的选择的影响。较高的相联度(E越大)的优点是降低了高速缓存由于冲突不命中出现抖动的可能性。不过,较高的相联度会造成较高的成本,而且很难使速度变快。每一行需要更多的标记位、LRU状态位和额外的控制逻辑。增加了复杂性,同时也增加了不命中处罚。相联度的选择最终变成了命中时间和不命中处罚之间的折中。

写策略的影响:直写高速缓存比较容易实现,而且能使用写缓冲区,它独立于高速缓存,用来更新存储器。此外,读不命中开销没这么大,因为它们不会处罚存储器写。另一方面,写回高速缓存引起的传送比较小,它允许更多的到存储器的带宽用于执行DMA的I/O设备。此外越往层次结构下面走,传送时间增加减少传送的数量就变得更加重要。

一般而言,高速缓存越往下走,越可能使用写回而不是直写。

高速缓存行、组和块有什么区别?

块是一个固定大小的信息包,在高速缓存和主存(或下一层高速缓存)之间来回传送。

行是高速缓存中存储块以及其他信息(如:有效位、标记位)的容器。

组是一个或多个行的集合,直接映射高速缓存中的组只由一行组成,组相联和全相联高速缓存中的组是由多个行组成的。

在直接映射高速缓冲中,组和行确实是等价的,不过在相联高速缓存中,组和行是很不一样的,这两个词不能互换使用。

因为一行总是存储一个块,术语“行”和“块”通常互换使用。“行大小”实际上是“块大小”

好的程序员总是应该试着去编写高速缓存友好的代码。

确保我们的代码高速缓存友好的基本方法:

1、让最常见的情况运行的快。程序通常把大部分时间都花费在少量的核心函数上,而这些函数通常把大部分时间都花费在少量循环上。所以要把注意力集中在核心函数里的循环上,而忽略其他部分。

2、在每个循环内部使缓存不命中数量最小。在其他的条件,例如加载和存储的总次数,相同的情况下不命中率较低的程序运行的更快。

对局部变量的反复使用时好的,因为编译器能够将他们缓存在寄存器文件中(时间局部性)

步长为1的引用模式是好的,因为存储器层次结构中所有层次上的缓存都是讲数据存储为连续的块。

在对多维数组进行操作的程序中。空间局部性尤其重要。

存储器山:一个程序从存储器系统中读数据的速率被称为读吞吐率,或者有时称为读带宽。如果一个程序在s秒内读n个字节,那么这段时间内的读吞吐率就等于n/s,典型的是以MB/s为单位的。

我们编写一个程序,它从一个紧密程序循环中出发,系列读请求,那么测量出的读吞吐率能让我们看到对于这个读序列来说的存储系统的性能。

分块使得代码更难阅读和理解。它最适合优化编译器或者频繁执行的库例程,能够产生巨大性能收益。

分块的大致思想是将一个程序中的数据结构组织成称为快的组块。块是指一个应用级的数据组块,而不是高速缓冲存块。

存储系统:较小、较快的设备靠近顶部。较大、较慢的设备靠近底部。

程序员理解存储器层次结构本质能编写出更好的程序。推荐技术:

1、将注意力集中在内部循环上,大部分存储和访问都发生在这里。

2、通过按照数据对象存储在存储器中的顺序来读数据,从而使得你程序中的空间局部性最大。

3、一旦从存储器中读入一个数据对象,就尽可能多低使用它,从而使你程序中中的时间局部性最大。

4、不命中率只是确定你代码性能的一个因素,存储器访问数量也扮演重要角色,有时需要两者之间做一下折中。

基本存储技术包括:RAM(随机存储器)、ROM(非易失性存储器)和磁盘。

RAM有两类:SRAM(静态RAM)快一些,但也贵一些。DRAM(动态RAM)慢一点,也便宜一点。

程序员可以通过编写有良好空间和时间局部性的程序来动态地改进程序的运行时间。利用基于SRAM的高速缓存存储器特别重要。

                                                




 











      

































































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
【作 者】(美)Randal E. Bryant,(美)David O'Hallaron著;龚奕利,雷迎春译 【出版发行】 北京市 【ISBN号】7-5083-2175-8 【页 数】 830 ; 23cm 【原书定价】85.00 【主题词】计算机系统(学科: 高等学校) 计算机系统 【中图法分类号】TP3 (工业技术>自动化技术、计算机技术>计算技术、计算机技术) 【参考文献格式】(美)Randal E. Bryant,(美)David O'Hallaron著 深入理解计算机系统. 【内容提要】 国外经典计算机科学教材:全书分别讲述了数据表示;C程序的机器级表示、处理器结构、程序优化存储器层次结构、链接、虚拟存储器和存储器管理、网络编程和并发编程等诸多内容。 作者简介:Randal E. Bryant 1973年获得密歇根大学(University of Michigan)学士学位,随即就读麻省理工学院(Massachusetts Institute of Technology)的研究生院,并在1981年获计算机博士学位。他在加州理工学院(California Institute of Technology)做了三年助教,从1984年至今一直是卡内基梅隆大学(Carnegie Mellon)的教师。他现在是计算机科学的主任级教授和计算机科学系的系主任。他同时还受邀于电子和计算机工程系。 他从事本科和研究生计算机系统方面课程的教学超过20年。在讲授计算机体系结构课程多年后,他开始把关注点从如何设计计算机转移到程序员如何在更好地了解系统的情况下编写出更有效和更可靠的程序。他和O’Hallaron教授一起在卡内基梅隆大学开设了“计算机系统导论”课程,那便是此书的基础。他还教授一些算法和编程方面的课程。 Bryant教授的研究涉及帮助硬件设计者验证其系统正确性的软件工具的设计。其中,包括几种类型的模拟器,以及用数学方法来证明设计正确性的形式化验证工具。他发表了100多篇技术论文。包括Intel、Motorola、IBM和Fujitsu在内的主要计算机制造商都使用他的研究成果。他还因他的研究获得过数项大奖。其中包括Semiconductor Research Corporation颁发的两个发明荣誉奖和一个技术成就奖,美国计算机学会(Association for Computer Machinery,ACM)颁发的Kanellakis理论与实践奖,还有电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)授予的W. R. G. Baker奖和50年金质奖章(a Golden Jubilee Medal)。他同时是ACM和IEEE的院士。 David R. O’Hallaron 1986年在维吉尼亚大学(University of Virginia)获得计算机科学的博士学位。在通用电气工作一段时间后,于1989年作为系统科学家成为卡内基梅隆大学的教员。他目前是计算机科学系和电子及计算机工程系的副教授。 他教授一些本科生和研究生的计算机系统方面的课程,例如计算机体系结构、计算机系统绪论、并行处理器设计和Internet服务。和Bryant教授一起,他开设了“计算机系统导论”课程,那便是此书的基础。 O’Hallaron教授和他的学生从事计算机系统领域的研究。特别的,他们开发了一些软件系统,帮助科学家和工程师在计算机上模拟自然界。其中最著名的是Quake项目,一群计算机科学家、土木工程师和地震学家致力于在强烈地震中预测大地运动的能力,这些强烈地震包括南加洲、古巴、日本、墨西哥和新西兰的大地震。同Quake项目中其它人员一起,他获得了CMU计算机科学院颁发的Allen Newell优秀研究奖章。他为Quake项目创立的基准程序,183.equake,被SPEC(Standards Performance Evaluation Corporation)选入非常有影响的SPEC CPU和OMP(Open Mp)基准程序包中。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值