Computer Architectrure: Quantitative Approch 第三章第一、二节

Instruction-Level Parallelsim: Concepts and Challenge

本章主要介绍一些概念:

数据依赖性
1. 指令i的结果用在了指令j的计算里
2. 指令j依赖于指令i的结果,指令j依赖于指令k的结果(传递性)

命名依赖性
1. j在i后执行,j会对i中使用的寄存器进行改写
2.j和i顺次执行会写入相同的地址

数据冒险:囊括了数据依赖性和命名依赖性两种情况,主要有WAW WAR RAW

控制依赖性:执行的结果依赖于跳转指令,因此需要将跳转指令做完才能知道正确结果。

Basic Compiler Techniques for Exposing ILP

这章主要探索用编译技术来增强处理器运行ILP的能力,其对处理器的静态issue和规划非常重要。本节可以配合Appendix H一起读,Appendix H里面会提到更复杂的编译和硬件设计。

Basic Pipeline Scheduling and Loop Unrolling

为了保持流水线的工作效率,我们必须让流水线同时处理一些互不想关的指令。为了避免流水线的阻塞,对于两条有依赖性的指令,下一条指令必须与上一条分开一定时间(等于上一条指令执行时间)。对编译器处理能力的评定标准包括在一个程序中可以处理ILP的数量和流水线中功能单元间的延迟。

在这里插入图片描述

Figure3.2展示了我们在本章假设的浮点数单元各个执行过程的延迟。假设使用的是稳定的五步流水线,这样跳转有一个时钟的延迟。我们假定这个过程完全流水线化并且流水线各个步骤可以复制。这样在任意时钟都可以处理任意运算,没有结构阻塞。

在这一部分,我们将展示编译器如何利用转换循环来提高有效ILP数量。我们主要使用下面这条例子,将标量加到向量上。
在这里插入图片描述
因为每个相加的过程是独立的,所以这是一个平行循环。我们在Appendix H中对这个过程系统研究,并且提供了在编译过程中可以测试各个循环是否独立的方法首先我们来看一下这个循环的性能,并且展示我们如何使用循环平行来提高循环性能。(各个过程的性能分析如Figure3.2所示)

首先将示例转化成MIPS语言,如下所示R1初始化为向量的最大地址;F2是标量数值s;R2的数值是最先算出的,是一个地址8(R2)相应于向量最起始的数值。

没有进行流水线规划的直接MIPS代码如下:
在这里插入图片描述
我们首先来看看用规划好的流水线优化这个代码能将效率提升多少。

在这里插入图片描述

在上面的示例中,我们用7个时钟完成了循环和写回一个向量单元。但实际上真正运算的时间仅为7个时钟中的3个时钟(ld,add,st),余下的四个时钟是用来做上面的循环(daddui, bne和两个时钟的延迟)。为了降低这四个时钟,我们需要编写比上面指令数更多的运算。

一个用来提高与跳转指令和上层指令相关指令数量的方法是loop unrolling.解循环简单的多次复制了循环主体并调整循环终止代码。

解循环也可以来做流水线进程规划。因为它减少了指令跳转,所以可以把不同迭代放到同一进程里。基于以上,我们可以通过在循环体中做一些额外的无依赖性指令来减少数据延迟。如果我们在解循环过程中简单的复制指令,结果使用相同的寄存器可能会阻止我们有效地调度循环。因此我们希望对于不同的指令使用不同的寄存器,这样也提高了寄存器的需求。

在这里插入图片描述
Loop Unrolling 示例

在实际的编译过程中,我们并不知道循环的上限。假设上限是n,我们希望循环复制k次。我们会做一对连续的循环。先将循环执行(n mod k)次,然后解循环,让循环进行n/k次。(第四章也有相似的技术叫做strip mining,是用来编译向量计算的)当n值很大的时候大多数执行时间是用来解循环。

在上面的示例中,解循环来提高指令效率是通过减少指令循环数,当然同时也会提高代码大小。解循环可以将下面示例中流水线的代码效率提高多少呢?

在这里插入图片描述

在展开循环上获得的收益大于原始循环上的收益。这个提升来自于解循环带来了更多可以用来最小化延迟的计算,比如上面这个示例就没有延迟。用这种方法处理循环必须实现ld和st相互独立并且可以相互交换。

Summary of the Loop Unrolling and Scheduling

通过这章和Appendix H的内容,我们可以学习到利用各种带有ILP的软硬件技术来充分发挥处理器函数单元的作用。这里面大多技术的关键是何时以及如何对指令进行重排序。在我们的示例中,做了很多指令顺序的调整。(进行手动调整)在实际应用中,处理器必须通过软件或硬件利用合理的方式进行调整。为了实现最终的解循环代码,我们需要最以下转换:
1. 通过看循环内的操作除了判断循环继续下去的代码间没有依赖性来确保解循环可操作。
2.在使用相同寄存器来做不同计算的情况下,使用不同的寄存器来避免延迟。
3.在ld和st相互独立的情况下,确保ld和st的位置可以相互交换。这种情况需要避免两者使用的内存地址相同。
4.对代码进行规划后要保证在任何情况下都可以和原代码处理得到相同的结果。
所有这些转换的关键需求是理解两条指令为什么相关同时要知道如何重排序。

有三个不同的影响限制了解循环带来的效果:(1)每一次展开循环都会减少摊销在循环上的损耗,(2)代码尺寸限制,(3)编译器的限制。我们首先考虑循环摊销。假设解循环了四次,这样会有足够多的平行可以使得指令处理时无延迟。实际上14个时钟里只有两个时钟是循环开销,也就是用来维持index的DADDUI和用来定义循环的BNE.如果解循环了八次,那开销将会从1/2个时钟每条指令变成1/4个时钟每条指令。

第二个限制是解循环带来的代码尺寸上升。对于很大的循环来说,代码尺寸是关键问题特别是在会引起指令cache miss率提升的情况。

另一个比代码尺寸还重要的问题是指令展开和调度会引起寄存器数量不足,这个问题一般称作register pressure. 这个问题产生的原因是指令调度使得需要使用的数值增加了。在大幅度调度之后,将所有的值都放到寄存器里不太可能。转换后的代码虽然理论上更快,但是因为存在寄存器短缺,所以会失去原有的优势。如果不展开代码,大幅度调度会被跳转指令严重限制,因此register pressure不是个问题;而将代码解循环和大幅度调度一起使用会引起这个问题。这个问题在多issue处理器中会很严峻,因为会有更多独立的可同步执行的指令序列。通常情况下,使用复杂的高级转换已经导致了现代编译器的复杂度显著增加。(我们在进行生成详细代码之前很难评估其是否存在潜在的优化)

解循环是一种简单但有用的方法,它可以用于增加可以有效调度的直线代码片段的大小。 这种转换在各种处理器中(无论是目前为止已经研究过的简单流水线还是本章稍后探讨的多issue超标量和VLIW)都非常有用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值