MIT 6.172 L4 Assembly Language and Computer Architecture Part 2
课程主页:
课程视频:
https://www.bilibili.com/video/BV1wA411h7N7
这次回顾第四讲第二部分,主要回顾计算机体系结构。
计算机体系结构概述
本讲主要内容:
- 超标量处理
- 乱序执行
- 分支预测
一个简单的5级处理器
5级处理器的框图
每条指令通过5个阶段执行:
- 指令提取(IF):从内存中读取指令。
- 指令解码(ID):确定用于执行指令的单元,并提取寄存器参数。
- 执行(EX):执行ALU操作。
- 内存(MA):读/写数据存储器。
- 回写(WB):将结果存储到寄存器中。
体系结构改进
从历史上看,计算机架构师旨在通过两种方式来提高处理器性能:
- 通过同时执行多个指令来利用并行性。
- 示例:指令级并行性(ILP),向量化,多核。
- 利用局部性以最大程度地减少数据移动。
- 示例:缓存。
流水线指令执行
硬件处理器通过寻找机会在不同的流水线阶段同时执行多条指令来利用指令级并行性。
理想情形:
每个流水线阶段都在执行不同的指令。
实际情形:
各种问题都可能阻止一条指令在其指定的周期内执行,从而导致处理器流水线失速(stall)。
流水线失速的原因
三种类型的危害(hazards)可能会阻止一条指令在其指定的时钟周期内执行。
- 结构危害:两条指令试图同时使用同一功能单元。
- 数据危害:一条指令取决于流水线中先前指令的结果。
- 控制危害:关于控制流的决定(即条件跳转)会延迟获取和解码要执行的下一条指令。
数据危害的来源
由于$i$和$j$之间的依赖关系,指令$i$可能会对后来的指令$j$产生数据危害。
真实依赖关系(RAW):指令$i$写入了指令$j$读取的位置。
反依赖关系(WAR):指令$i$读取指令$j$写入的位置。
输出相关性(WAW):指令$i$和$j$都写入相同的位置。
超标量处理
复杂操作
一些算术运算在硬件中实现起来很复杂,并且等待时间长:
硬件如何适应这些复杂的操作?
复杂流水线
想法:将单独的功能单元用于复杂的操作,例如浮点运算。
从复杂到超标量
给定这些额外的功能单元,处理器如何进一步利用ILP?
想法:每个周期获取并发出多个指令,以保持单元忙碌。
乱序执行
Bypassing
Bypassing允许指令在将其参数存储到GPR中之前先读取其参数:
数据依赖性例子
如果硬件必须按顺序执行所有指令,则执行需要多长时间?
数据流图
我们可以将指令之间的数据依赖关系建模为数据流图:
所以如果不按照顺序执行所有指令,而是按照数据流图的形式执行,可以得到下图:
消除名称依赖
想法:如果可以更改目标寄存器的名称,则可以消除WAR依赖性。
指令6不再依赖于长时间等待操作!
新的数据流图:
效果如下:
小结
处理器使用两种技术来减轻数据危害的性能损失:
- 重命名寄存器将删除WAR和WAW依赖关系。
- 乱序执行可减少由于RAW依赖性而导致的性能损失。
即时寄存器重命名
硬件如何克服WAR和WAW依赖关系?
想法:体系结构实现的物理寄存器比ISA指定的寄存器多得多。
维护从ISA寄存器到物理寄存器的映射,即Renaming table和物理寄存器:
动态指令重排
发布阶段使用称为重排序缓冲区(ROB)的循环缓冲区动态跟踪指令之间的数据依赖性,具体可以参考课件72-82。
重新排序和重命名总结
总结:硬件重命名和重新排序实际上是有效的。
- 尽管在汇编代码中存在明显的依赖性,但是通常只有真正的依赖性会影响性能。
- 可以使用数据流图对依赖关系进行建模。
分支预测
控制危害
如果处理器遇到条件跳转(又称分支),会发生什么情况?
指令获取阶段需要知道分支的结果。
在执行阶段之后就知道分支的结果。
投机(speculative)执行
- 为了处理控制危险,处理器要么在分支处停顿,要么通过推测执行。
- 例如遇到分支时,假设它未被占用,并继续正常执行。
- 如果以后发现分支已被采用,则撤消投机。
- 问题:
- 撤销计算对吞吐量的影响就像停顿一样。
- 现代处理器使用分支预测器来提高推测执行的效率。
- 提取阶段专用于硬件来预测分支的结果。
- 现代分支预测器在95%的时间内都是准确的。
简单分支预测
- 想法:硬件维护一个表,将分支指令的地址映射到其结果的预测。
- 预测被编码为2位计数器:
- 根据关联分支的实际结果更新预测计数器:
- taken:增加计数器。
- not taken:减少计数器。
处理危害总结
- 处理器使用几种策略在运行时处理危害:
- 拖延(Stalling):冻结早期的流水线阶段。
- 绕过(Bypassing):将数据计算后立即传递到较早的流水线阶段。
- 乱序执行(Out-of-order execution):先执行后一条指令,再执行前一条指令。
- 寄存器重命名(Register renaming):通过更改其寄存器操作数来消除依赖性。
- 推测(Speculation):猜测依赖性的结果,只有在猜测不正确的情况下才重新开始计算。