美文网首页
初窥cpu体系

初窥cpu体系

作者: ye2012 | 来源:发表于2018-07-12 14:27 被阅读0次

    RISC和CISC

    CPU的主要功能是通过执行各种指令实现的,CPU支持的所有指令构成了这个CPU的指令集。其中指令集分为RISC(精简指令集)和CISC(复杂指令集)

    • CISC
      复杂指令集通常有非常多的指令,每个指令都可以独立完成较复杂的功能,这样减少上层语言编程的压力,简化工作。
      但是不同的指令需要不同的时钟周期,使得流水线无法匀速执行,拖慢快指令,影响整台机器的效率。

    • RISC
      精简指令集的指令比较少,功能单一,大部分可以在一个时钟周期执行结束,通过不同的指令组合,从而完成一个完整的大功能。
      CPU的指令简单,会将指令组合的工作移交给上层编译器,导致代码量庞大。

    • 总结
      目前的CPU对于这两个指令集的区别越来越小,虽然大部分CPU支持的是CISC,但是底层却是转化成RISC指令,而这个译码的阶段是异步且十分快速,所以这个开销可以忽略。这样也解决了CISC的效率差异。

    流水线处理

    • 流水线
      流水线技术是将一条指令的执行过程分解成独立的多步,每一步由cpu不同的部分处理。在一条指令在执行某一步的同时,其他指令也在执行其他步。就像一条流水线,指令按照一定的顺序排好,执行的每一步相当于一个工厂对指令加工。这样看起来就像是多个指令在同时执行。
      指令
      指令处理的基础6步:
      ( 1 ) 取指令。C P U从高速缓存或内存中取一条指令。
      ( 2 ) 指令译码。分析指令性质。
      ( 3 ) 地址生成。很多指令要访问存储器中的操作数,操作数的地址也许在指令字中,也许要经过某些运算得到。
      ( 4 ) 取操作数。当指令需要操作数时,就需再访问存储器,对操作数寻址并读出。
      ( 5 ) 执行指令。由A L U执行指令规定的操作。
      ( 6 ) 存储或"写回"结果。最后运算结果存放至某一内存单元或写回累加器A。
    • 超流水线
      在流水线上,完成一条指令的平均周期,就是所有步骤中处理时间最长的那个时间。因为只要所有的步骤都完成,整个流水线就会前进,而所有步骤完成的标志就是处理最慢的完成。所以只要缩短步骤的时间,就可以加快处理指令的时间。
      通过增加流水线的长度,减少每个步骤的时间,可以增加CPU的频率,但是需要额外的锁存器。而且深度过长导致出现预测失败的回滚开销也变得更大。
      例子:Pentium IV具有31级的流水线,指令的执行效率却赶不上只有14级流水线的Pentium M
    • 超标量
      超标量(superscalar)是指在CPU中有一条以上的流水线,并且每时钟周期内可以完成一条以上的指令,这种设计就叫超标量技术。
      超标量CPU试图在一个周期取出多条指令并行执行,是通过内置多条流水线来同时执行多个处理,其实质是以空间换取时间。但由于指令之间的相关性,即后一条指令需要前一条指令的结果,超标量CPU的性能并不是随流水线增加线性增长,大约一个周期能执行1.2条指令,而为了取得这20%的性能改善,超标量CPU需要增加大量的硬件电路来调度这些同时取出的指令,比如寄存器重命名,预约站,重排序缓冲区等。

    乱序执行

    如果按照指令本来的顺序执行,一旦有一个操作需要消耗较长时间,比如需要从内存加载数据,那整个cpu都会陷入等待,所以乱序执行就被发明了。

    • 保留站


      保留站.jpg

      解码单元解码后的指令不是直接送到流水线,而是根据各自的指令种类,将解码后的指令送往各自的保留站中保存下来。如果操作数位于寄存器中,就把操作数从寄存器中读出来,和指令一起放入保留站。相反,如果操作数还在由前面的指令进行计算,那么就把那条指令的识别信息保存下来。
      然后,保留站把操作数齐备、可执行的指令依次送到流水线进行运算。即使指令位于前面,如果操作数没准备好,也不能开始执行,所以保留站中的指令执行顺序与程序不一致(乱序)。另外,保留站会监视执行流水线输出的结果,如果产生的结果正好是等待中的指令的操作数,就将其读入,这样操作数齐备后,等待中的指令就可以执行了。

    • 反向依赖
      使用保留站可以实现指令的乱序执行,但是可能会导致最终结果的错误
    LD r1,[a]; ←将内存的变量a 读入到寄存器r1(加载)
    ADD r2,r1,r5; ←r1与r5相加,保存到r2
    SUB r1,r5,r4; ←r5减去 r4,保存到 r1
    

    读取变量a的过程可能会有延迟,而第二步也会因为r1无法取得无法进入流水线。但是第三步的r4,r5却是已经有的数据,这样第三步会先进入流水线执行,计算的结果覆盖了r1的值,从而导致计算第二步的时候结果错误。
    这种前面指令用到的数值有可能被后面的指令覆盖的情况叫做反向依赖。
    解决办法就是寄存器重命名。

    • 寄存器重命名
      通过将相同的逻辑寄存器映射到不同的物理寄存器中,达到消除反向依赖的作用。
    LD p11,[a]; ←将内存的变量读入寄存器p11 (r1)
    ADD p12,p11,r5; ←p11 (r1)与 r5相加,保存到p12 (r2)
    SUB p13,r5,r4; ←r5减去 r4,保存到 p13 (r1)
    

    在第一次给r1赋值的时候,用到的是p11寄存器,第二步等待的也是p11。但是第三部虽然是给r1赋值,但是这里写入的不是之前的p11,而是新的寄存器p13。这样就可以避免读后写(WAR)的依赖,即先写再读也不会有问题。不过要注意的是,这里会存储一个逻辑寄存器到物理寄存器的映射表,当分配新的物理寄存器时,要更改对应关系。指令提交的时候要把物理寄存器的内容写回到逻辑寄存器。

    分支预测

    • 静态分析
      • Early Static Branch Prediction:总是预测接下来的指令不走跳转分支,即执行位于跳转指令前方相邻(比当前指令晚执行)的指令。
      • Advanced Static Branch Prediction:如果所跳转的目标地址位于跳转指令的前方(比当前指令晚执行),则不跳转;如果所跳转的目标地址位图跳转指令的后方(比当前指令早执行)则跳转。这种方法可以很有效地应用在循环的跳转中
      • Hints Static Branch Prediction:可以在指令中插入提示,用于指示是否进行跳转。x86架构中只有Pentium 4用过这种预测方式。
    • 动态分析
      分支预测缓冲区
      通过记录历史跳转,预测本次是否跳转。这里的记录分为两种。
    • Branch History Table(BHT)
      记录分支历史信息的表格,用于判定一条分支指令是否token,可以用每个bit的0、1代表未跳转和跳转。每条指令可以使用n个bit位存储n次跳转信息。经过一系列的实验,发现2bit的效率最高,这个2bit计数器叫做饱和计数器。
    • BTB
      用于记录一条分支指令的跳转地址。指令地址的bit数一般是32,远大于BHT每个指令的bit数,因此BTB存储的信息也少于BHT。如果一个指令不跳转,就不会存储在BTB中。
      指令在取址阶段会在BTB中寻找是否有对应的跳转地址,如果有,则预测跳转,利用在BTB存储的地址取得对应的跳转之后的指令。
      如果BTB中没有,再去BHT中查找,根据结果执行对应的预测。


      2位饱和计数器

    cpu缓存

    • 缓存级别


      内存体系
      1. 寄存器:在每个核心上,有很多用于计算的寄存器单元。访问这些寄存器只需要一个时钟周期,这构成了对执行核心来说最快的内存
      2. 内存排序缓冲(Memory Ordering Buffers (MOB) ):MOB由一个64长度的load缓冲和36长度的store缓冲组成。这些缓冲用于记录等待缓存子系统时正在执行的操作。store缓冲是一个完全的相关性队列,可以用于搜索已经存在store操作,这些store操作在等待L1缓存的时候被队列化。在数据与缓存子系统传输时, 缓冲可以让处理器异步运转。当处理器异步读或者异步写的时候,结果可以乱序返回。
      3. L1 缓存:L1是一个本地核心内的缓存,被分成独立的32K数据缓存和32K指令缓存。访问需要3个时钟周期,并且当指令被核心流水化时, 如果数据已经在L1缓存中的话,访问时间可以忽略。
      4. L2缓存:L2缓存是一个本地核心内的缓存,被设计为L1缓存与共享的L3缓存之间的缓冲。L2缓存大小为256K,主要作用是作为L1和L3之间的高效内存访问队列。L2缓存同时包含数据和指令。L2缓存的延迟为12个时钟周期。
      5. L3缓存: 在同插槽的所有核心都共享L3缓存。L3缓存被分为数个2MB的段,每一个段都连接到槽上的环形网络。每一个核心也连接到这个环形网络上。地址通过hash的方式映射到段上以达到更大的吞吐量。根据缓存大小,延迟有可能高达38个时钟周期。在环上每增加一个节点将消耗一个额外的时钟周期。缓存大小根据段的数量最大可以达到20MB。L3缓存包括了在同一个槽上的所有L1和L2缓存中的数据。这种设计消耗了空间,但是使L3缓存可以拦截对L1和L2缓存的请求,减轻了各核心私有的L1和L2缓存的负担。
      6. 主内存:在缓存完全没命中的情况下,DRAM通道到每个槽的延迟平均为65ns。具体延迟多少取决于很多因素,比如,下一次对同一缓存 行中数据的访问将极大降低延迟,而当队列化效果和内存刷新周期冲突时将显著增加延迟。每个槽使用4个内存通道聚合起来增加吞吐量,并通过在独立内存通道上流水线化( pipelining )将隐藏这种延迟。
      7. NUMA:在一个多插槽的服务器上,,需要访问的内存可能在另一个插槽上,并且通过 QPI总线访问需要额外花费40ns。
    • 缓存方式

      • Cache Line
        Cache Line可以简单的理解为CPU Cache中的最小缓存单位。目前主流的CPU Cache的Cache Line大小都是64Bytes。
      • N-Way Set Associative


        缓存存储结构

        将整块缓存分成N等份,每一份再以cache line为单位等分,每个等份称为组。每个组内有一个标签用来区分不同的内存地址,前面存储了一些标志位用来标志内存的状态。


        内存地址的映射关系
        最后两位是字节的偏移,即32位系统每个字包含4个字节。前面两位是块内偏移,也就是在cache line中所在字的位置。在前面8位是索引,即是在上面缓存所分的N等份中的哪一个。最后剩下的就是标签,用来在每个组中标志不同的内存。
    • 缓存一致性
      缓存一致性是用于保证多个CPU cache之间缓存共享数据的一致,MESI则是缓存一致性协议中的一个。

      • 状态
        1. modify:当前CPU cache拥有最新数据(最新的cache line),其他CPU拥有失效数据(cache line的状态是invalid),虽然当前CPU中的数据和主存是不一致的,但是以当前CPU的数据为准
        2. exclusive:只有当前CPU中有数据,其他CPU中没有改数据,当前CPU的数据和主存中的数据是一致的
        3. shared:当前CPU和其他CPU中都有共同数据,并且和主存中的数据一致
        4. invalid:当前CPU中的数据失效,数据应该从主存中获取,其他CPU中可能有数据也可能无数据,当前CPU中的数据和主存被认为是不一致的
      • 转换
        MESI协议中,每个cache的控制器不仅知道自己的操作(local read和local write),通过监听也知道其他CPU中cache的操作(remote read和remote write)。对于自己本地缓存有的数据,CPU仅需要发起local操作,否则发起remote操作,从主存中读取数据,cache控制器通过总线监听,仅能够知道其他CPU发起的remote操作,但是如果local操作会导致数据不一致性,cache控制器会通知其他CPU的cache控制器修改状态

    总结

    CPU的体系是一个比较复杂的问题,因为每一家CPU厂商都在按照自己的步骤进行优化,但是这些理论还是比较通用的。可能平时工作中基本用不到这些东西,但是多了解一点还是有好处的,比如遇见一个诡异的问题的时候,代码层面解释不通,那很可能就是CPU在作祟。

    相关文章

      网友评论

          本文标题:初窥cpu体系

          本文链接:https://www.haomeiwen.com/subject/adtcpftx.html