[转帖]计算机体系结构-存储指令的加速

计算机,体系结构,存储,指令,加速 · 浏览次数 : 0

小编点评

**全乱序方法** 全乱序方法是一种高效、正确地执行load/store指令的三种常规方法。该方法利用 store buffer 来缓存加载指令,并通过 load forwarding 从 buffer 中获取数据。 **load 预测** load 预测是一种预测一条load指令是否会和前序store指令发生读后读冒险的方法。通过缓存加载指令的信息, store 预测可以减少排空流水线的次数。 **Wait Table** Wait Table 是一个用于实现load预测的结构。当 store 预测发现一条load指令和前序store指令发生读后读冒险时,它将将错误指令的信息写入 Wait Table 中。当 load 指向 Wait Table 时,处理器会检查 Wait Table 中的信息,并如果发现错误,就会排空流水线。 **load forwarding** load forwarding 是一个从缓存中获取数据到执行单元的机制。当 store 预测发现一条load指令和前序store指令发生读后读冒险时,它将将错误指令的信息写入 store buffer 中。当 load 指向 store buffer 时,处理器会从 store buffer 中获取数据并将其传递给执行单元。 **总结** 全乱序方法是一种高效、正确地执行load/store指令的三种常规方法。该方法利用 store buffer 来缓存加载指令,并通过 load forwarding 从 buffer 中获取数据。Wait Table 和 load forwarding 共同减少排空流水线的次数,并通过 load 预测 减少错误指令的信息写入。

正文

https://zhuanlan.zhihu.com/p/507619114

  

记分牌Tomasulo算法通过拷贝数据到保留站、广播计算结果和寄存器重命名等方法实现了计算指令的乱序执行,但是这两个算法均不涉及存储指令(load和store)。实际上,在一个乱序核中执行存储指令还需要一套独立的机制/方法。本文会介绍三种处理存储指令方法,这三种方法的性能和复杂度是递增的。

1、存储指令的特殊之处

和对寄存器进行操作的的指令所遇到的困难一样,存储指令间也存在三种数据冒险——写后读、写后写、读后写。访问寄存器的指令可以通过寄存器重命名的方法来消除假冒险,并且可以通过旁路、阻塞和插入气泡等方式来处理写后读冒险,但是对存储指令而言,事情没这么简单。

load/store指令同样存在三种冒险

在RISC中,存储指令一般通过寄存器间接寻址的方式访问存储器,因此在地址计算出来之前,机器不知道存储指令将访问哪部分主存——这区别于访问寄存器,当指令只涉及寄存器时,在指令解码阶段机器就可以获知所有寄存器编号,从而为后续重命名、旁路、阻塞等操作做准备。

看下面这个例子,只有当ST和LD指令的目的地址不一样的时候,这两条指令才不存在写后读冒险,但目的地址在执行阶段之前是得不到的,因此当两条指令发生冒险,机器没法在发射阶段阻塞load指令(因为机器没有指令的目的地址,它不知道load实际上发生冒险了),只能任由load发射、执行,然后出错。

可能发生冒险的两条ls指令

换句话说,因为存储指令的相关性隐藏得很深,机器没办法预先掌握存储指令之间的相关性,因此无法像处理计算指令一样处理存储指令。本文接下来会介绍三种处理存储指令的方法,第一种方法按程序顺序执行存储指令,而第二第三种方法则可以乱序执行部分/全部load指令。

2、完全顺序

在这种方法中,load和store指令完全按照程序顺序执行。这是一种最保守的做法,可以保证指令之间不发生冒险,但是这样会牺牲处理器的性能。

load和store指令起效果的时间点不一样,load指令的结果在执行结束的时候就可以获得,而store指令实际上要到提交的时刻才可以把数据存入到存储器,因此如果要完全按顺序执行,(在比较恶劣的情况下)load指令必须等到前面的store指令提交完才能发射,而且store指令有可能写空,如果数据块不在cache内,store就可能要把数据块从主存中调入cache,然后写入,这个过程是极漫长的。

因为“完全顺序执行”牺牲了指令的并行性,让指令付出了不必要的等待,所以顺序执行虽然省事,但是效果很不好,高性能的处理器核是不会使用这种方法的。

3、部分乱序

这种方法让store指令顺序执行,store和store之间的load指令乱序执行。下图是一个例子,指令A和E之间的BCD三条指令可以在A指令提交之后乱序执行。

部分乱序

这个方法的过程:

  • 先发射A指令,A发射之后立马计算目标地址,计算完毕之后store指令信息和地址信息进入一个FIFO队列(一般把这个FIFO称为store buffer),然后处理器就可以唤醒、发射BCD指令(只要BCD指令准备好);
  • BCD指令被唤醒发射之后,会先计算自己的目标地址,然后查询store buffer,如果发现store buffer中有store指令的地址和自己相同,就说明自己和store发生了写后读相关,此时load指令可以直接取store指令的数据(一般把这个过程称为load forwarding);如果没有写后读相关,load指令就正常访问D-cache。

这里有一个问题,指令E是否可以在BCD指令没有发射完的情况下发射?

如果允许E在BCD之前发射,那么FG就可以和BCD一起被乱序地发射,但是这会带来另一个问题。如果AE同时存在于store buffer之中,那么BCD在查询store buffer的时候,就不仅要查看是否有和自己地址相同的store指令,还要查看同址指令是否是自己的前序指令——显然的,即使E指令和BCD指令的地址相同,也不会发生写后读冒险。

因此如果允许E提前发射,就要给各条指令附加年龄信息,以表明指令的先后顺序。这里先举一个例子,以演示“部分乱序”的过程。

部分乱序执行结构

在上图中,load指令计算完地址之后会拿着load addr查询store queue(也即store buffer),同时还会去访问D-cache;如果store queue中有同址指令,且“age比较模块”显示该指令比自己老,那么机器会根据match信号选出store buffer中的数据(即value)作为data out;如果store指令的数据还没到位,那就输出wait信号,表示load需要等待store的数据。

记录上面例子中的age信息有三种方法供选择:

  • 使用指令PC来标定顺序,大部分时间这个方法是work的,但是如果store指令之前有一条向前跳转的指令,那么这条store指令的PC可能比实际上先于自己执行的load指令的PC要小,此时load指令就会误以为这条store指令是自己的前序指令,那么程序就会出错;
  • 使用ROB来标定顺序,指令在进入发射队列的同时会按顺序进入ROB,因此ROB保存了指令的先后关系,可以用ROB的编号来标定年龄。但是不是所有的指令都是load、store指令,load、store指令拿到的ROB编号一定是稀疏的,因此ROB编号位宽会比较大,这样就造成比较电路使用较大的数据位宽,浪费了面积和功耗;
  • 在解码阶段,为每条load、store指令分配一个编号,解码阶段指令都是顺序的,因此可以为指令分配编号,并让编号随指令进入发射队列和store buffer,这样做是比较好的。

在实际操作中,后面两种方法是可以使用的,但是比较复杂。 更简单的方法是禁止E指令越过前面的load指令。这样store buffer中就只存在前序的store指令,load在检查store buffer的时候只会碰到前序store,这样就不用标记彼此的年龄。但是这个方法显然会降低处理器的性能,没有很好地利用指令的并行性。

4、全乱序

这里的全乱序指的是load全乱序,store仍是顺序的。在这种方法中load指令无需等待任何store指令,只要load自己的数据准备好,就可以发射出去。

很显然,这么做是会产生写后读冒险的,即store指令还没发射,load指令就已经发射、读数。如何解决这个问题?

可以在执行单元中增加一个load buffer,其中储存着所有已经发射的load指令和其目的地址。当store指令发射,它会去查询load buffer,如果发现load buffer中有同址load指令,且该指令比自己年轻,那就说明该指令已经读取到错误的信息了,这种情况很麻烦,需要特别处理。

当store在load buffer中发现错误的load指令,因为指令的结果可能已经被后序计算指令使用,所以流水线在这个时候可能已经存有很多错误的结果。这个时候最简单的办法是立刻暂停执行段之前的流水线,然后继续执行store,等到store指令提交,就把store指令之后的所有指令(即ROB中的所有指令)都清除掉(同时还需要恢复处理器的状态,如寄存器映射表,因此对于store指令,也需要保存checkpoint,checkpoint的介绍见寄存器重命名),然后重新取指。Alpha 21264处理器把这称为排空流水线。

下图是store查询load buffer的结构示意。store指令在计算完地址之后会拿着store addr查询load queue(即store buffer),综合age信息之后,如果发现有load指令出错,机器会输出一个flush信号,即表示需要冲刷流水线(排空流水线)。

store指令查询load buffer

如果频繁地排空流水线,那处理器的效率就大大地降低了,因此我们不能让这种错误频繁出现。怎么做?

Alpha 21264使用load预测的方法来减少错误。什么是load预测?即预测一条load指令是否会和前序store指令发生读后写冒险,如果预测为“会发生冒险”,则该load指令就不会被唤醒电路从发射队列中唤醒,也就不会被发射到执行单元。

为什么可以这么做?load预测的依据是什么?其实在一个固定的程序里,store和load的关系是固定的,load不会无缘无故地取数,有时候load是去取我们事先存在主存中的数,而有时候load指令是去取刚刚store指令存进主存的数,一旦一条load指令去取了之前store存进主存的数据,那么之后它很有可能会继续这么干,因此可以用一个数据结构保存这个信息,只有当load指令被预测为“与前序store指令无关”,load指令才被允许唤醒、发射。

下面是Alpha 21264在load/store部分的微结构,用这个微结构来说明如何实现load预测。重点关注D-cache1和D-cache2,D-cache1段供load/store指令互相检查buffer,以及从cache中取数,D-cache2则进行tag比较,确定是否cache hit,另外,注意到Tag Compare有一条线连到一个叫做Wait Table的结构,这个结构是做什么用的?

Alpha 21264的load/store指令执行流水

这个Wait Table记录了load指令的预测信息。一旦发现一条load和前序store发生写后读冒险,机器就会在D-cache2段把这个错误指令的信息写进Wait Table;当一条load指令解码时,处理器会查询这个Table,并把Table中的预测信息附加到流水线中,当load指令等待发射,机器会检查这个附加信息,如果附加信息显示该指令“会发生写后读冒险”,那么处理器就不会发射该指令,直到该指令的所有前序store都发射完毕。

故事告一段落,上面主要讲解了如何解决load优先store发射所产生的问题。如果load指令老老实实地在store指令之后发射,那么事情就简单了。只需要load指令在发射之后查询store buffer,如果发现有同址store指令,就把该指令的数据取过来,而不去读D-cache,这个做法就是第三节所说的load forwarding。

这里我们再总结一下“全乱序”方法是如何高效、正确地执行load/store的:

  • 引入store buffer,让load指令查询store buffer,并用load forwarding直接从buffer中取数,消除写后读冒险;
  • 引入load buffer,让store指令查询load buffer,如果发现错误,则排空流水线,以此消除写后读冒险;
  • 顺序执行store指令,以消除写后写冒险;
  • store指令只有在提交时才改写存储器,因此前序load指令总可以读到正确的值,因此顺序提交的机器不用考虑读后写冒险;
  • 引入Wait Table,实现load预测,减少排空流水线的次数。

5、总结

本文讲解了处理load/store指令的三种常规方法,介绍了load buffer、store buffer、load forwarding、load 预测、Wait Table和排空流水线等概念。通过这些概念和记分牌、Tomasulo等乱序执行算法,我们可以正确、高效地执行大部分指令。

与[转帖]计算机体系结构-存储指令的加速相似的内容:

[转帖]计算机体系结构-存储指令的加速

https://zhuanlan.zhihu.com/p/507619114 记分牌和Tomasulo算法通过拷贝数据到保留站、广播计算结果和寄存器重命名等方法实现了计算指令的乱序执行,但是这两个算法均不涉及存储指令(load和store)。实际上,在一个乱序核中执行存储指令还需要一套独立的机制/方

[转帖]计算机体系结构-(2)内存数据保持和刷新

https://zhuanlan.zhihu.com/p/433151653 本人lino,即将毕业的研究生,在此记录下学习过程。本次记录跟随是苏黎世邦理工大学的计算机体系结构课程。 当在memory中存储数据时,数据的保留是个问题,可能会丢失这个数据。因此本次内容围绕着DRAM进行深度探索,了解其

[转帖]计算机体系结构-(3)内存系统的挑战和机遇

https://zhuanlan.zhihu.com/p/434689028 本人lino,即将毕业的研究生,在此记录下学习过程。本次记录跟随是苏黎世邦理工大学的计算机体系结构课程。 我们需要解决许多由内存阻碍的问题,内存中数据交互存在着安全和隐私的问题,因此这对于内存来说也是一个巨大的挑战。针对这

[转帖]003、体系结构之TiKV持久化

TiKV架构和作用 数据持久化分布式一致性MVCC分布式事务Coprocessor coprocessor : 协同处理器。 可以将一些SQL计算交给TiKV处理。不需要将TiKV所有数据通过网络发送给TiDB Server RocksDB 任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也

[转帖]TiFlash 源码阅读(一) TiFlash 存储层概览

https://cloud.tencent.com/developer/article/1988629 背景 本系列会聚焦在 TiFlash 自身,读者需要有一些对 TiDB 基本的知识。可以通过这三篇文章了解 TiDB 体系里的一些概念《 说存储 》、《 说计算 》、《 谈调度 》。 今天的主角

[转帖]计算机体系结构-(1)多核内存竞争问题

https://zhuanlan.zhihu.com/p/432234496 本人lino,即将毕业的研究生,在此记录下学习过程。本次记录跟随是苏黎世邦理工大学的计算机体系结构课程。 Memory Performance Attacks 相比于单核系统,在多核系统里面,我们想要的是: N times

[转帖]计算机体系结构-(1)多核内存竞争问题

https://zhuanlan.zhihu.com/p/432234496 本人lino,即将毕业的研究生,在此记录下学习过程。本次记录跟随是苏黎世邦理工大学的计算机体系结构课程。 Memory Performance Attacks 相比于单核系统,在多核系统里面,我们想要的是: N times

[转帖]计算机体系结构-(4)内存系统的问题解决方向

https://zhuanlan.zhihu.com/p/436875536 本人lino,即将毕业的研究生,在此记录下学习过程。本次记录跟随是苏黎世邦理工大学的计算机体系结构课程。 本文将介绍一些宽泛的Memory的解决方案。首先是Make memory and controllers more

[转帖]计算机体系结构-重排序缓存ROB

https://zhuanlan.zhihu.com/p/501631371 在现代处理器中,重排序缓存(Reorder Buffer,即ROB)是一个至关重要的概念,一个标准的乱序执行处理器在其多个流水线环节中都会涉及重排序缓存,而Tomasulo算法一文也指出Tomasulo算法的最大缺点可以由

[转帖]计算机体系结构-cache高速缓存

https://zhuanlan.zhihu.com/p/482651908 本文主要介绍了cache的基本常识、基本组成方式、写入方法和替换策略,在基本组成方式和替换策略两节给出了较为详细的硬件实现方法,并不流于空泛,并且补充了SRAM和三态门等与硬件实现息息相关的知识。更高阶的cache优化方法