⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter4.htm

📁 mips run 中文版
💻 HTM
📖 第 1 页 / 共 4 页
字号:
<BR>从九十年代早期cache设计在cpu同一块芯片上,高速cpu的性能很大程度上是有他们的cache系统性能决定的。现在许多的系统(尤其是嵌入式系统,需要节约cache的大小和内存的性能),CPU有50-60%的时间时在等待cache的再次填充。就这点来说将CPU性能翻倍将增加应用程序15-25%的性能。 
<BR>cache性能取决于系统在等待cache再次填充的总时间。你可以将之归结为两个参数产生的结果: 
<BR>a.cache没有命中的几率:是CPU(取指令或数据存取)在cache中没有命中,从而需要内存中取的比例。 
<BR>b.cache没有命中需要替换造成的延时:这个延时是从内存中取数替换后到CPU的流水线继续下去的时间。 
<BR><BR>当然这没有必要很好的测量。举个例子,x86的CPU的寄存器个数很少,所以同样的程序编译给x86使用会比给MIPS使用多很多的数据存取。当然x86使用堆栈来代替寄存器给这些额外存取使用;而这堆栈位置将是内存中使用非常频繁的区域,对应cache的话使用效率会非常高。通过大块特殊的程序,我们就能获得cache没有命中的次数。 
<BR>上面提到的意见将给下面指出的几种提高系统运行速度的显而易见的方法很有帮助。 <BR><BR>a. 减少cache没有命中的几率: 
<BR>1.让cache变得更大。这是最有效的,当然也算是最昂贵的。在1996年,64K的cache会占据高速嵌入CPU一半的硅面积甚至超过,所以要使cache的大小翻倍,你只有等待摩尔理论发明在相同的面积上做更多的门。 
<BR>2.增加cache的组相连。值得增加到四组,再增加下去提高就几乎看不到了。 
<BR>3.增加另外层次的cache。当然这将使计算变得更加复杂。除了多义子系统的复杂外,第二层cache的非命中率会被抑制很多;主cache已经可以撇去CPU重复访问数据行为的cream。为了使其物有所值,第二层cache必须比主cache大得多(一般来说是八倍或者更大),并且第二层cache的访问时间必须比内存快(两倍或者更快)。 
<BR>4.优化你的程序从而减少非命中率。如果工作处于实践阶段,这就很难说清楚了。对于小的程序优化就比较简单,但对于琐碎的程序就很费力了。但迄今为止还没有人做出一个工具可以优化任何程序。看4.12节。 
<BR><BR>b. 减少cache替换的处罚: 
<BR>1.加快CPU获得第一个字的数据。DRAM内存系统需要必须做许多发动工作,才能提供数据很快。使内存和CPU之间更加紧密,减短它们之间的路径,这样数据在它们之间来回才会更快。 
<BR>注意这是唯一可以在便宜的系统中应用,并且效果不错。不过荒谬的是它很少被注意到,也许是因为它需要在CPU接口和内存系统设计之间考虑更多的综合因素。当CPU设计者在设计芯片的接口时不情愿处理这些问题,很可能因为他们的工作已经够复杂的了! 
<BR>2.增加内存burst的宽度。这是传统上通常被应用的,是昂贵的技术,用两个或者更多的内存系统来交替存储数据;当初始化完后你就可以交替着从每一个内存系统中取数据,这样带宽好像翻倍了。第一个这样内存技术的应用是,是在1996年出现的同步DRAM(SDRAM)。SDRAM修改了DRAM的接口,提供更大的带宽。 
<BR><BR>c. 
尽早的重新起动CPU:这个简单的方法是在排列cache替换的数据时,从CPU没有命中的数据开始,当数据一到就马上重新启动CPU。cache的替换可以和CPU运行并行。MIPS 
CPU从R4x000开始就应用这项技术,用一个子缓冲区来存放cache要替换进来的数据,并能那个字的数据是最先需要的。但只有R4600和其派生出来的CPU才体现出这项技术的好处。 
<BR>激进的方式是让CPU绕过取数操作继续执行下去;取数的操作由总线接口控制器控制,CPU继续运行直到它需要的数据被存放到相应的寄存器重。这被称之为没有阻碍取数,从R10000和RM7000开始被应用。 
<BR>更激进的方法是,可以执行任何后续代码,只要它不依赖于还没有取来的数据,R10000就可以不按照指令的顺序来执行。这类CPU应用这项技术非常彻底,不仅仅是应用到取数还应用到计算和跳转指令。 
<BR><BR><BR>4.12 修改软件使Influence Cache效率更高 
<BR>很多时候我们都在程序访问可知地方的基础上工作,并且我们是公平的在不强制的工作方式上操作。对于绝大多数目的我们同样可以假设,访问是恰当的随机分布式。对于工作站就必须能够支持执行足够的应用程序,这是一个公平的假设。但当一个嵌入式系统运行一个简单的应用程序,没有命中的清况好像是因为特殊的程序经过特殊的编译造成的。这是很有诱惑的,如果我们能是应用程序代码能在系统的方式下提高cache的效率。为了了解这是怎样处理的,你将cache没有命中分类,按照它们产生的原因: 
<BR>a. 第一次访问:所以任何数据必须从内存中读入。 <BR>b. 
替换:cache的大小是有限的,所以当你的程序没有运行多久就会出现cache没有命中,需要替换掉一部分合法的数据。当程序运行时,cache就会不停的替换掉数据然后在取。你可以通过使用大的cache和减小程序的大小来使替换和没有命中减到最少(其实就是程序大小和cache大小的比例)。 
<BR>c. 
从实际来讲,cache通常没有超过四路组相连的,所以对于任何程序地址在cache中最多有四个位置可以存放;对应直接映射就只有一个,二路组相连的cache就有两个(和组相连比较thrashing会丢失减少速度的可能;但绝大多数研究者建议一个四路组相连的cache在way的选择上几乎不回丢失性能)。 
<BR>如果你的程序会非常频繁的使用n段空间的数据,而这n段空间的地址的低位又非常接近,那么它们就会使用相同的line。如果n大于cache的路数,那么cache的没有命中也会非常频繁,因为每一段空间的数据会不停的将cache内的其他空间的数据挤出去。 
<BR><BR>明白上面的知识,那么怎样针对程序做变化使它针对cache运行的更好? <BR>a. 
使程序更小:如果你能做到的话,这是个很好的主意。你可以使用适当的编译器优化(外来的优化通常是程序更大)。 <BR>b. 
让程序中经常执行的那部分更小:访问密度在一个程序中不总是平均分布的。通常会有相当一部分代码几乎不被用到(错误处理,不明系统的管理),或者只被用到一次(初始化代码)。如果你能将这些很少用到的代码剥离出来,对于剩余的程序你就能得到一个很好的cache命中率。 
<BR>资格访问的方式有利于将一些频繁使用的程序区分出来,并固定放在内存的某一位子,以减少要运行时的放置。这样至少这些经常使用的程序不会因为cache的位置而相互碰撞。 
<BR>c. 
强迫一些重要的代码或数据常驻cache:一些机器的能允许一部分cache保护它们所拥有的数据不被替换。这部分代码一般是中断处理或是其他自关重要的软件中确定要执行的。这些代码或数据一般被保留在二路组相连cache的其中一组中(这样当系统重其后,cache就像是一个直接映射的cache). 
<BR>我很怀疑这个方法的生存能力,我也不知道有那些研究支持它的有效性。系统重起后的损失很可能大于执行那些保留代码的获利。cache上锁很可能就像一个不确定的市场工具来限死顾客对cache启发式特性的渴望。这个渴望是可以理解的,随着程序越快越复杂越大,但cache毕竟只是影响结果的一部分因数。 
<BR>d. 
安排程序避免碰撞:上面提到的让程序的执行部分变的更小,这对于我来说太难维护所以不是个好的主意。而且对于组相连的cache(尤其是两路的)使这个方式更加没有意义。 
<BR>e. 让那些很少用到的数据和代码不经过cache:这看起来很有吸引力,让cache只给那些重要的代码或数据服务,排除那些只用一次或很少使用的代码和数据。 
<BR>但这几乎总是一个错误。如果数据真的很少使用,那么它们不可能一开始就在cache中。因为cache取数时总是以一条line的长度4或16字为单位,所以即使是传送只使用一次的数据也能有很高的速度;burst替换比一个字的访问几乎不多花时间,并且能给你免费提供另外的3或5个字的数据。 
<BR><BR>简而言之,我们将介绍下面的内容作为一个起点(除非你已经有很多实践和很深的想法,你才可以放弃)。开始我们先认为除了I/O寄存器cache都是打开的,并且很少使用远程内存。在你试着做预测前,先搞明白cache对你的应用程序有什么启发。第二在硬件上排除任何问题。也没有任何软件辅助收回因为高的cache替换率和小的内存带宽而损失的性能。尝试从新组织软件而降低cache的非命中率,而不能增加其长度和使其变复杂,但也要明白一开始收获是很小并且来之不易。也试着在硬件上做优化。 
<BR><BR><BR><BR>4.13 Write Buffers and When You Need to Worry 
<BR>通常使用write-though cache的32位MIPS 
CPU,其每一个写操作都会立刻直接写到主存中,如果CPU要等待每一个写操作完成才能继续,这将是是一很大的性能瓶颈。 
<BR>C语言程序编译给MIPS使用,平均有10%的指令是存储指令;但这些操作可能可以趋向于合并为burst,举个例子在一个函数开始时的现场保护(保存一些寄存器)。DRAM内存通常有这样的特征,一组中第一个写的通常会花费很长时间(这些CPU一般是5至10个时钟周期),而第二个和后边的就会相应很快。 
<BR>如果CPU简单地等待每一个写操作完成,这对性能地打击很大。所以通常会提供一个回写地缓冲区,先入先出地存入要写地数据和地址。 
<BR>使用write-through cache的32位MIPS 
CPU很倚重回写缓冲区。在这些CPU中,在CPU的时钟频率达到40MHz时能缓冲四次的队列将很难提供很好的缓冲。 <BR>后来的CPU(有write-back 
cache)的缓冲区能直接保存需要回写的line,并且提高非cache写的时间。 
<BR>许多回写缓冲区操作的时间对于软件来说是透明的。但有时候编程人员要注意下面的情况: <BR>a. I/O寄存器访问的时间:这对所有MIPS 
CPU都有影响。当你执行一个向I/O寄存器的写操作,这会有一个不能确定的延时。和I/O系统之间的其他通讯可能会很快,举个例子在你告诉设备不要再产生中断后,你还是可能会看到一个活跃的中断。在其他例子中,如果I/O寄存器在一个写操作后需要一定时间来恢复,那么在你开始计算这个延时前,你必须保证回写缓冲区是空的。这儿你必须保证CPU等待直到回写缓冲区腾空。定义子程序来做这项工作是个好习惯;子程序叫wbflush(),这是个传统。看后面的4.13.1节,如何实现。 
<BR><BR>上面描述了在任何MIPS R4x000(MIPSIII ISA)上可能发生的。还针对整个IDT 
R3051家族,和绝大多数流行的嵌入式CPU。但在一些早期的32位系统中,更怪的事可能发生: <BR>a. 
读操作赶上写操作:当一条取数指令(非cache或是没有命中cache)执行时,回写缓冲区不是空的。CPU需要选择:是等写操作完成还是将内存接口给取操作使用?让取操作先做将提高效率,因为CPU需要等待直到取的数据到来。这是一个好的机会,写操作被压倒,但它后面还是可以和CPU并行。 
<BR>最初的R3000硬件将这个选择留给系统硬件来决定。从IDT开始的绝大多数MIPSI 
CPU不允许读操作压倒写操作,写操作有没有条件的优先权。绝大多数MIPSIII 
CPU不允许读操作被压倒,但软件也没有必要对此进行过多的考虑。看8.4.9节关于sync指令的描述。 <BR>如果你确认你的MIPS I 
CPU没有无条件的写的优先权,那么当你在处理I/O寄存器时,必要的地址检测也不能帮到你;因为早期的一个向不同地址的写操作还没有完成,那么这时候的取操作就会产生错误。在这中情况下你就需要调用wbflush()。 
<BR>b. 
字节合并:当缓冲区注意到一些部分字的写操作是写向相同的字地址,缓冲区会将这些写操作合并成一个简单的写操作。这并没有被所有的R3051家族的CPU所采用,因为它可能在对I/O寄存器的写操作时产生错误。 
<BR>如果将你的I/O寄存器映射成每个寄存器对应一个独立的字地址,这就不是一个坏注意了。但你不可能总这样做。 <BR><BR>4.13.1 执行wbfush 
<BR>除非你的CPU是上面提到的特殊类型的一种,你能保证在执行针对任何地址的取操作时,回写缓冲区是空的(这会暂停CPU直到写操作完成,取操作也完成)。但这样效率不高;你可以通过使用最快的内存来最小限度来克服一些。 
<BR>对于那些从来都不想考虑这些的人,一个写操作后面紧跟着一个针对相同地址的取操作(如果你是运行在MIPS 
III或其后面的CPU,需要在两个指令间加入sync指令),需要先清空回写缓冲区(很难看到如果CPU没有这个动作也能执行正确)。 
<BR>一些系统通过硬件信号来指明FIFO是不是空的,连到输入接口让CPU能立刻得知。但不是到目前为止的CPU都这样做。 
<BR><BR>CAUTION!一些系统通常在CPU外面也有写操作的缓冲区。任何总线或内存接口自夸的写加速也会出现相同的特征。写缓冲区在CPU外和在CPU内一样,也会给你带来相同的问题。 
<BR><BR><BR><BR>4.14 其他的关于MIPS CPU <BR>虽然你可能永远也不用知道这些,但我们还是有许多理由要谈到这些。 
<BR><BR><BR>4.14.1 多处理器的cache特征 
<BR>这个书的讨论将仅仅针对单CPU的系统。感兴趣的可以读相应的文档(Sweazey和Smith著,1986)。 <BR><BR>4.14.2 Cache 
Aliases 
<BR>这个问题只会影响这样一类的cache,用于产生index的地址和存放在tag内的地址不一样。在R4000类型CPU的主cache中,index是由虚拟地址长生而tag内存放的是物理地址。这对性能很有好处,因为cache查询和地址转化可以并行,但这也可能导致aliases。 
<BR>绝大多数这些CPU可以按照4KB一页的大小来转化地址,而cache大小是8KB或者更大。两个不同的虚拟地址可以映射成对应一个物理地址,而且这是两个连续页,我们可以假设它们开始地址分别是0KB和4KB。如果程序访问地址0KB,那么数据会被读入到index为0的cache中。我们在假设要地址4KB来访问相同的数据,cache就又会从内存中取数并保存到cache中,但这次index却为4KB。这时在cache中针对相同数据有两个备份,一个被改变了,另一个却不会受到影响。这就是cache的alias。 
<BR>MIPS第二层cache是物理地址来产生index和被存放到tag中,所以它们不会产生上面的问题。 
<BR>不过,避免这个问题比改正它更容易。如果任意两个不同的虚拟地址能产生相同的index,那么这个问题就不会出现了。对于4KB一页,只要保证用来产生index的最低12位地址一致;只要保证不同虚拟地址对应的物理地址页大小等于主cache大小的模。如果你能保证虚拟地址是64KB的倍数,这就不可能产生这个问题,你也不会遇到麻烦。 
<BR><BR><BR> </P><BR></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -