摘要:对于偏软件的程序员,时常对指令的乱序执行,寄存器重命名,超标量处理器,等名词感到疑惑。本文将对这些知识进行初步介绍,为你解开这些疑惑。本文是我阅读Computer Architecture- A Quantitative Approach的学习笔记,文章中在原书例子的基础上,加上了我自己的一些理解。写作本文的目的是学习的总结和备忘,同时与爱好者进行交流,因此错误之处,期待各位斧正。
由于本书主要以MIPS为目标平台进行介绍的,因此例子也是MIPS汇编代码,但是熟悉X86汇编的朋友也不用担心,在文章开头,我特别对几条汇编指令进行了解释,相信汇编语法不会成为你阅读本文的障碍。
在工业生产中广泛使用流水线来提高生产效率,同样的,在CPU中流水线可以极大的提高指令执行效率。假设拥有五级流水线如下所示:
  • 取指令周期(Instruction Fetch Cycle(IF)) 根据PC(Program Counter)寄存器,从内存中读取读取下一条指令,然后PC = PC+4 (32位系统。)
  • 指令译码周期(Instruction Decode Cycle(ID)) 根据取回的指令,解析操作码(例如:转移操作,算术运算等。)以及操作数(例如寄存器。)。由于在译码阶段确定了目标操作数,因此对于转移指令,只需要增加一个比较单元就可以在该阶段确定是否需要转移。例如:
    ; Jump to Label if R4=0
    BEQZ R4, Label
    ; Jump to Label if R3 != R4
    BNE  R3, R4, Label
    
  • 指令执行周期(Execution/effective address cycle(EX)) 算术逻辑部件(ALU)根据操作码对操作数进行运算,分为一下几种情况:
  • 内存引用的有效地址运算 计算基址寄存器+偏移量形成有效地址。例如下面的指令:
    ; 在指令执行阶段需要计算 R1+0
    LD R4,0(R1)
    ; 在指令执行阶段需要计算 R1+12
    SD R4,12(R1)
    
  • 寄存器-寄存器操作 根据操作码对两个目标寄存器进行运算。例如下面的指令:
    ; 在指令执行阶段需要计算 R2+R3
    DADD 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   取指 Stall Stall 译码 执行 回写    
    AND R6, R1, R7     取指 Stall Stall 译码 执行 回写  
    OR R8, R1, R9       取指 Stall Stall 译码 执行 回写
    XOR R10, R1, R11         取指 Stall Stall 译码 执行
    时钟周期
    指令 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, Label
    DADD 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

流水线学习笔记(一)相关推荐

  1. 【FPGA】流水线学习笔记

    下面的内容来自:<从算法设计到硬件逻辑的实现> 所谓流水线设计实际上就是把规模较大.层次较多的组合逻辑电路分为几个级,在每一级插入寄存器组暂存中间数据.K 级的流水线就是从组合逻辑的输入到 ...

  2. 【学习笔记】微体系结构-单周期、多周期、流水线

    [学习笔记]微体系结构-单周期.多周期.流水线 前言 一.单周期 单周期直通计算机的内核结构 寄存器-寄存器数据通路 1.Load操作 2.Store操作 3.跳转指令 4.条件分支 示例 性能分析 ...

  3. 【硬件架构的艺术】学习笔记(4)流水线的艺术

    目录 写在前面 4. 流水线的艺术 4.1 介绍 4.2 影响最大时钟频率的因素 4.2.1 时钟偏移 4.2.2 时钟抖动 4.3 流水线 读书笔记汇总 写在前面 这个博客系列是对最近阅读的书籍&l ...

  4. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  5. CUDA学习笔记之 CUDA存储器模型

    CUDA学习笔记之 CUDA存储器模型 标签: cuda存储bindingcache编程api 2010-12-14 01:33 1223人阅读 评论(0) 收藏 举报 分类: CUDA(26) GP ...

  6. setup.s 分析—— Linux-0.11 学习笔记(二)

    更新记录 版本 时间 修订内容 1.0 2018-4-14 增加了"获取显示模式"这一节,AL取值的表格 标题: setup.s 分析-- Linux-0.11 学习笔记(二) 老 ...

  7. 《Go语言圣经》学习笔记 第九章 基于共享变量的并发

    <Go语言圣经>学习笔记 第九章 基于共享变量的并发 目录 竞争条件 sync.Mutex互斥锁 syn.RWMutex读写锁 内存同步 syn.Once初始化 竞争条件检测 示例:并发的 ...

  8. 3.2)深度学习笔记:机器学习策略(2)

    目录 1)Carrying out error analysis 2)Cleaning up Incorrectly labeled data 3)Build your first system qu ...

  9. ELK学习笔记之Logstash详解

    0x00 Logstash概述 官方介绍:Logstash is an open source data collection engine with real-time pipelining cap ...

最新文章

  1. 在实施OKR之前,你必须先了解这7点
  2. Redhat9五笔输入法安装
  3. matlab ktrlink,大神们,怎么设置滑动滑动条然后出来的图形也跟着变化?
  4. 【51NOD1287】加农炮
  5. CSS3实践之路(六):CSS3的过渡效果(transition)与动画(animation)
  6. 对scala函数总结
  7. 鸟哥Linux计算退伍时间,发现《鸟哥的Linux基础篇》中有个脚本还能再完善点。...
  8. 过年用计算机弹奏,过年实用,你可能需要的亲戚称呼计算器
  9. opencv图像分析与处理(11)- 频率域滤波消除周期噪声
  10. (PC+WAP)绿色小学学校网站源码 pbootcms中小学教育培训机构网站模板
  11. ask调制流程图_ASK调制及相干解调电路设计.doc
  12. 解压报错gzip: stdin: not in gzip format
  13. VS2005 执行控制台程序的时候,窗口一闪就没了的问题
  14. echarts世界地图(含中国省份地图),中国省份下钻到县,点击按钮返回上一级
  15. PS 色相、饱和度、明度
  16. 免费地图资源(持续更新)
  17. 用c语言找出图片中黄颜色部分,测试:6种玫瑰颜色找出你的恋爱基因,神准!...
  18. 如何提升自己的宣传效果?从这两个点开始
  19. 局域网内远程控制开机工具NetWaker
  20. 蛋白结构分析实操教程

热门文章

  1. 01 计算机、程序和Java概述
  2. BT配对/取消配对示例
  3. PDFgetXPDFgetN 软件分享
  4. 【附源码】计算机毕业设计SSM日租房管理系统
  5. 【系统分析师之路】系统分析师必知必会(数据库上篇)
  6. Kanzi学习之路(1):Kanzi的简介和安装
  7. 【信息汇总】南开大学计算机考研
  8. Windows更新网站被黑还是被ARP病毒捉弄了?Virus.Win32.AutoRun.am
  9. libjpeg库移植与使用
  10. 计算机网络实验指导书谢希仁,计算机网络(谢希仁)实验指导书.doc