流水线学习笔记(一)
来源:互联网 发布:java 生成svg矢量图 编辑:程序博客网 时间:2024/04/29 22:24
- 取指令周期(Instruction Fetch Cycle(IF)) 根据PC(Program Counter)寄存器,从内存中读取读取下一条指令,然后PC = PC+4 (32位系统。)
- 指令译码周期(Instruction Decode Cycle(ID)) 根据取回的指令,解析操作码(例如:转移操作,算术运算等。)以及操作数(例如寄存器。)。由于在译码阶段确定了目标操作数,因此对于转移指令,只需要增加一个比较单元就可以在该阶段确定是否需要转移。例如:
; Jump to Label if R4=0BEQZ R4, Label ; Jump to Label if R3 != R4BNE R3, R4, Label
- 指令执行周期(Execution/effective address cycle(EX)) 算术逻辑部件(ALU)根据操作码对操作数进行运算,分为一下几种情况:
- 内存引用的有效地址运算 计算基址寄存器+偏移量形成有效地址。例如下面的指令:
; 在指令执行阶段需要计算 R1+0 LD R4,0(R1) ; 在指令执行阶段需要计算 R1+12SD R4,12(R1)
- 寄存器-寄存器操作 根据操作码对两个目标寄存器进行运算。例如下面的指令:
; 在指令执行阶段需要计算 R2+R3DADD R1,R2,R3
- 寄存器-立即数操作 根据操作码对寄存器和目标操作数进行运算:例如下面的指令:
; 在指令执行阶段需要计算 R2+0x10;DADD R1, R2,0x10
- 内存访问周期(Memory Access(MEM)) 根据操作码(读/写)以及前面计算得到的有效地址,对内存进行读写操。例如下面的指令:
LD R4, 4(R1)SW R3, 4(R1)
- 回写周期(Write-Back Cycle(WB))对于寄存器-寄存器操作指令来说,把结果写入目标寄存器。例如:
; R2+R3的计算在指令执行阶段已经完成,而结果保存在ALU部件中,在回写周期运算结果被送到R1。DADD R1, R2, R3
对于内存读取来说(LD指令),把在内存访问周期得到的结果保存到目标寄存器中,例如:LD R4, 4(R1)
在这个简单的流水线实现方案中,各类指令的操作周期如下: - 转移指令需要两个周期(取指令,指令译码。)。
- 内存写入(Store)指令需要四个周期(取指令,指令译码,指令执行/有效地址计算,内存访问。)。
- 寄存器逻辑运算需要四个周期(例如:DADD R1, R2, R3)(取指令,指令译码,指令执行/有效地址计算,回写。)
- 其它指令需要五个周期(取指令,指令译码,指令执行/有效地址计算,内存访问,回写。)。 下面我们举例来说明这个流水线的工作情况:
时钟周期 指令 1 2 3 4 5 6 7 8 9 指令 i取指译码执行访存回写 指令 i+1 取指译码执行访存回写 指令 i+2 取指译码执行访存回写 指令 i+3 取指译码执行访存回写 指令 i+4 取指译码执行访存回写时钟周期 指令 1 2 3 4 5 6 7 8 9 指令 i取指译码执行访存回写 指令 i+1 取指译码执行访存回写 指令 i+2 取指译码执行访存回写 指令 i+3 取指译码执行访存回写 指令 i+4 取指译码执行访存回写时钟周期 指令 1 2 3 4 5 6 7 8 9 指令 i取指译码执行访存回写 指令 i+1 取指译码执行访存回写 指令 i+2 取指译码执行访存回写 指令 i+3 Stall取指译码执行访存回写指令 i+4 取指译码执行访存时钟周期 指令 1 2 3 4 5 6 7 8 9 DADD R1, R2, R3取指译码执行回写 DSUB R4, R1, R5 取指译码执行回写 AND R6, R1, R7 取指译码执行回写 OR R8, R1, R9 取指译码执行回写 XOR R10, R1, R11 取指译码执行回写时钟周期 指令 1 2 3 4 5 6 7 8 9 DADD R1, R2, R3取指译码执行回写 DSUB R4, R1, R5 取指StallStall译码执行回写 AND R6, R1, R7 取指StallStall译码执行回写 OR R8, R1, R9 取指StallStall译码执行回写XOR R10, R1, R11 取指StallStall译码执行时钟周期 指令 1 2 3 4 5 6 7 8 9 DADD R1, R2, R3取指译码执行回写 DSUB R4, R1, R5 取指译码执行回写 AND R6, R1, R7 取指译码执行回写 OR R8, R1, R9 取指译码执行回写 XOR R10, R1, R11 取指译码执行回写时钟周期 指令 1 2 3 4 5 6 7 8 9 LD R1, 0(R2)取指译码执行访存回写 DSUB R4, R1, R5 取指Stall译码执行访存回写 AND R6, R1, R7 取指Stall译码执行访存回写 OR R8, R1, R9 取指Stall译码执行访存回写时钟周期 指令 1 2 3 4 5 6 7 8 9 BNE R3, R4, Label取指译码 ...... 取指 Label:AND R6, R1, R7 取指译码执行访存回写 OR R8, R1, R9 取指译码执行访存回写时钟周期 指令 1 2 3 4 5 6 7 8 9 BNE R3, R4, Label取指译码 DADD R1, R2, R3 取指译码执行回写 Label:AND R6, R1, R7 取指译码执行访存回写 OR R8, R1, R9 取指译码执行访存回写在上面这个例子中,把一条无论转移是否发送都必须执行的指令放到延迟槽中,如果转移发生时,对该条指令的取指工作已经完成,于是继续执行这条指令,这并不影响程序原本的逻辑。虽然在这个我们的例子中,引入通过延迟槽之后,转移指令已经不会对流水线带来性能上的影响。然而在现实中却比本例复杂的多(例如Pentium4拥有20多级的流水线,又被称为超级流水线。),因此延迟槽只能相对的减小转移指令带来的性能损失。
转移预测分为静态转移预测和动态转移预测,静态转移预测就是,当CPU遇到转移指令时,总是假设转移条件满足(或者不满足),因此下一取指周期时总是把转移目标作为指令地址(或者总是顺序取值。)。而动态转移预测则是通过在CPU内部增加专门的硬件模块,例如:转移历史表,转移目标缓冲器等。静态转移预测需要编译器的支持,例如GCC提供的__builtin_expect()就是用于这个目的。现在我们假设某个构架的CPU采用静态转移预测,并且总是假定转移条件不满足,因此在遇到转移指令时也顺序取指令。那么对于下面的代码编译器有两种处理方式:
......if (var == 5) {var += 5;}.......
编译后,代码布局可能有一些两种可能:......# 假设var变量被分配到寄存器R3中BEQ R3, #5, Label......Label:DADD R3, R3, #5;......
或者是下面这个样子:......BNEQ R3, #5, LabelDADD R3, R3, #5;Label:......
对于顺序取指的静态转移预测来说,如果程序运行中转移条件(var==5)成立的次数远远大于不成立的次数,第二部分代码要优于第一部分代码,这时我们可以使用GCC提供的__builtin_expect()来通知GCC:
......if (__builtin_expect( (var == 5), 1) ) {var += 5;}.......
这样GCC就知道(var==5)这个条件成立的次数较多,于是按照前面第二种情况生成目标代码。
相反,如果转移条件(var==5)不成立的次数远远大于成立的次数,则一部分代码优于第二部分,此时可以使用:
......if (__builtin_expect( (var == 5), 0) ) {var += 5;}.......
这样就可以保证让GCC把按照前面第一种情况生成目标代码。最后,我们需要再次提醒的是:只有在目标平台的处理器和GCC同时支持的情况下,按照这种方式生成目标代码才有意义。
由于__builin_expect()看起来不太直观,于是在Linux内核中定义了likely()和unlikely():
#define likely(x) __builtin_expect(!!(x), 1)#define unlikely(x) __builtin_expect(!!(x), 0)/* 对于条件成立的次数多的,我们使用likely() */if (likely(var==5)) {......}/* 否则使用unlikely(). */if (unlikely(var==5)) {......}
双向推测执行需要更多的执行部件,CPU在遇到转移指令时,在两个方向上同时取指令执行,等地转移结果出来后,抛弃错误的那个方向上的结果。
来源:http://www.osplay.org/modules/article/view.article.php/14/c9
- 流水线学习笔记(一)
- opengl es 学习笔记(一):流水线
- Shader 学习笔记(一) 渲染流水线
- 流水线学习笔记(二)
- UnityShader入门精要学习笔记(一):渲染流水线
- Redis系列学习笔记11 流水线
- [Unity3D]Shader学习笔记之渲染流水线
- tensorflow学习笔记(四十二):输入流水线
- Shader学习笔记(三) GPU流水线
- Unity Shader入门精要笔记(一):渲染流水线
- arm的5级流水线的学习笔记
- spring学习笔记——spring框架bean装配流水线
- 表达式运算顺序引发的流水线的学习笔记
- pytorch学习笔记(四):输入流水线(input pipeline)
- pytorch学习笔记(四):输入流水线(input pipeline)
- Pipeline学习器流水线
- 软考(一)流水线
- spring学习笔记(16)趣谈spring 事件机制[2]:多监听器流水线式顺序处理
- JQuery實現分頁
- 自增问题的一点儿误区
- Java文件操作大全(二)
- linux socket编程一般模式
- 规范化的C++编程方法备忘录 整数声明
- 流水线学习笔记(一)
- WINCE MUI 多语言的支持
- Java文件操作大全(三)
- MySQL常用函数
- DSNetwork/Receiver 缓冲池实现机制分析
- Java文件操作大全(四)
- wince 控制面板添加应用程序
- 流水线学习笔记(二)
- SIFT/SURF算法的深入剖析——谈SIFT的精妙与不足