优化
参考了 intel 的优化手册。
通用优化
都是老生长谈的问题。代码排布是一个问题,主要是要考虑分支预测和 BTB 的问题,感觉和解耦合的前端有点关系,等下次有空看 BTB 的时候再去回顾。在这个部分中还提到了 loop buffer 的一些使用要点,其中提到了在一些情况下可以通过 loopbuffer 不断的提供一些指令来减少一些功耗。
执行的层面主要集中在对调度的使用、对内存访问的优化、对 store-load 前递的优化。这三个优化贯穿始终,并且是体系结构相关的。对调度的使用要尽量在每个周期内把功能单元的端口用完, store-load 的前递主要主要看体系结构定的规则,像 intel 中就详细规定了制定的规则,基本知道什么情况下的前递延迟少,为优化提供了指导。
intel 还有 4k-alias 的问题,这是内存乱序访问的问题,暂时还不清楚。
写合并,intel 貌似有专用的指令做写合并的优化。
simd 优化
主要是对 intel amx/sse 的使用。
对分支的优化,有分支的情况下尽量用条件传送替代,这是对于分支的优化。
store-load 前递的优化,向量也是可以 store-load 前递的。很多情况下我们想向量 store 之后来跟一个标量的 load 来进行访问,但是这个这个情况下不会触发前递,合适的方式是从向量 store 后跟一个向量的 load 来触发前递,然后再从向量寄存器中读取想要的数据。
数据对齐,向量的使用有建议的数据对齐要求,可以查看手册。
适当使用掩码,通过掩码进行向量内存的访问,避免跨页的访问,跨页的访问可以通过掩码消除掉。
调度的使用,对于调度的使用主要看向量能够有几个端口进行使用,每个端口能够支持哪些版本的向量指令。向量的实际硬件实现也不近相同,比如 256 可能有专用的 256 硬件实现,或者把 256 拆成两个 128 进行实现,很多时候能对向量的高一半低一半进行选取排序可能就跟实现有关。
选取操作的实现并不简单,从手册介绍来看这个操作实现的开销是很大的。选取操作有 shuffle 和 blend,前者适合复杂的排序选取,后者通过 0 1 适合简单的选取,手册上说最好用后者,前者适合复杂的情况。
amx
主要优化就是分块,还有对于内存访问的优化。分块什么的也是要根据 cache 的容量去算,把常用的驻留在 cache 里,同时需要兼顾到的是把核上的计算资源用满,不能让核上的资源浪费。不同的分块方案访问的内存资源什么应该都是可计算的。
常见的优化手段是 Non-Temporal load/store,也称为 stream load/store。这种 load/store 是 bypass cache 的, load 直接 by-pass l2 l3 直接到 l1 或者寄存器,到 l1 的话可能会被标记成为更快被驱逐。store 就是 bypass cache 到 dram 或者 store buffer。这种 stream load/store 的方法主要适合那些在短时间内不被访问的内存,防止他们污染 cache。如果在分块之后,单核只需要某个矩阵一次,就可以用这个。