流水线学习笔记(一)

来源:互联网 发布:java 生成svg矢量图 编辑:程序博客网 时间:2024/04/29 22:24
在工业生产中广泛使用流水线来提高生产效率,同样的,在CPU中流水线可以极大的提高指令执行效率。假设拥有五级流水线如下所示:
  • 取指令周期(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)(取指令,指令译码,指令执行/有效地址计算,回写。)
  • 其它指令需要五个周期(取指令,指令译码,指令执行/有效地址计算,内存访问,回写。)。 下面我们举例来说明这个流水线的工作情况: 时钟周期指令123456789指令 i取指译码执行访存回写    指令 i+1 取指译码执行访存回写   指令 i+2  取指译码执行访存回写  指令 i+3   取指译码执行访存回写 指令 i+4    取指译码执行访存回写时钟周期指令123456789指令 i取指译码执行访存回写    指令 i+1 取指译码执行访存回写   指令 i+2  取指译码执行访存回写  指令 i+3   取指译码执行访存回写 指令 i+4    取指译码执行访存回写时钟周期指令123456789指令 i取指译码执行访存回写    指令 i+1 取指译码执行访存回写   指令 i+2  取指译码执行访存回写  指令 i+3   Stall取指译码执行访存回写指令 i+4     取指译码执行访存时钟周期指令123456789DADD R1, R2, R3取指译码执行回写     DSUB R4, R1, R5 取指译码执行回写    AND R6, R1, R7  取指译码执行回写   OR R8, R1, R9   取指译码执行回写  XOR R10, R1, R11    取指译码执行回写 时钟周期指令123456789DADD R1, R2, R3取指译码执行回写     DSUB R4, R1, R5 取指StallStall译码执行回写  AND R6, R1, R7  取指StallStall译码执行回写 OR R8, R1, R9   取指StallStall译码执行回写XOR R10, R1, R11    取指StallStall译码执行

    时钟周期指令123456789DADD R1, R2, R3取指译码执行回写     DSUB R4, R1, R5 取指译码执行回写    AND R6, R1, R7  取指译码执行回写   OR R8, R1, R9   取指译码执行回写  XOR R10, R1, R11    取指译码执行回写 

    时钟周期指令123456789LD R1, 0(R2)取指译码执行访存回写    DSUB R4, R1, R5 取指Stall译码执行访存回写  AND R6, R1, R7  取指Stall译码执行访存回写 OR R8, R1, R9   取指Stall译码执行访存回写

    时钟周期指令123456789BNE R3, R4, Label取指译码       ...... 取指       Label:AND R6, R1, R7  取指译码执行访存回写  OR R8, R1, R9   取指译码执行访存回写 

    时钟周期指令123456789BNE 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

原创粉丝点击