Skip to content

向量体系结构

太复杂,根本看不懂。不过可以有一些简单的启示,针对某个特定的机型去编写向量的程序的时候要 先看优化手册,看看是怎么实现的。

再谈三种并行性:

  1. ILP:向量指令与向量指令之间的指令级并行。
  2. DLP:向量指令单次能够处理的数据并行度。
  3. MLP:内存级并行度。

最好的情况当然是三个都追求,当然很难做到就是了。向量的两种实现方式是顺序和乱序, 顺序的方式是逻辑简单点,向量部分可以把更多的面积投入到拓展到向量计算单元和计算单元的向量长度上去; 乱序本身要挖ILP,逻辑肯定更加复杂,使得计算单元的向量长度少点。

顺序实现

顺序实现一般做长向量的实现,就是功能单元多且内部向量宽度大。顺序实现就是假如后面的指令和前面的指令出现了 一定的依赖关系,是会被堵住的。这使得ILP被限制在队列头部的那几个指令,头部几个指令的依赖关系决定了ILP程度, 写得好的话,头部的可以组成链式的关系,向量能在硬件上做这种优化。这就对指令的排布提出了很大的要求, 顺序向量很依靠指令的排布形式。

MLP 对于顺序向量是个难题,因为后续的内存请求会被堵住,MLP 的程度可能就只能有队列头部的那几条指令, 对于局部性好的,那还行,因为局部性好,在 cache 里面,访问存储的时间相对可控。但是局部性差的,每次走内存的话, 性能就不是很好。并且顺序的情况下不是很好做预取,因为MLP低,cache 实际上看到的内存访问不是很多, 不好让预取器学习,所以顺序向量的情况下,可能软件预取的重要性会更重要。现在还有的做法是会有独立的 runahead 硬件,相当于直接去扫队列中的向量访存,算出地址做预取,这种方式的问题是,对于简单的访问模式,可能能扫出来, 但是向量load的地址是基于前序结果生成的,比如用shuffle指令,就没办法扫出来,shuffle指令往往对应的程序 特征是一些稀疏的程序。

乱序实现

乱序实现和正常的乱序实现没什么区别,逻辑变得非常复杂。寄存器重命名和乱序调度什么的都一样,只是实现是 非常复杂的,因为一个向量的寄存器长的可以拆成短的重命名,调度也可以一条拆成几条重命名,这个看具体实现。

DLP 方面,从 zen6 上 avx512 看,这种复杂性并不导致先进的公司因为复杂性而降低DLP,在乱序的同时,向量的 链式打包执行仍然存在的。

MLP 是提升最大的,可以乱序,乱序窗口内的内存都是可以 outstanding 的,访存的需求可以打的非常大, 这对cache的实现提出很大的要求,一者带宽要跟上,容忍能力要跟上,MSHR要做大,二是有机会能够识别 outstanding的特征做激进的预取。

同时在 titon 的实现中还看到,为了扩大 outstanding,向量甚至也做了乱序的假提交异步,这实际上 把乱序窗口的规模扩的更大了,性能有机会进一步提高。