编译器目前将 a+b+c+d
编译为 a+(b+(c+d))
。它应该使用 (a+b)+(c+d)
,因为后者可以乱序执行。
更广泛地说,我们应该平衡关联计算的树。
在md5block.go的第4轮中,使用针对单个计算类型的规则定制编译器,实现了15%的吞吐量提升。(详见下文。)
在我看来,是否可以通过精心设计的重写规则实现这一点,或者专用的优化是否更好,尚不清楚。但看起来在紧密的数学代码上可能有显著的性能优势。
我不打算进一步研究这个问题,但我真的希望有人能接手。
cc @randall77@martisch@FiloSottile@mmcloughlin@mdempsky
为了重现md5结果,请禁用优化的汇编程序,并添加以下重写规则:
(Add32 <t> c:(Const32) (Add32 d:(Load _ _) (Add32 x:(Xor32 _ _) a:(Add32 _ _)))) => (Add32 (Add32 <t> c d) (Add32 <t> x a))
并禁用此规则以避免无限循环:
(Add32 (Add32 i:(Const32 <t>) z) x) && (z.Op != OpConst32 && x.Op != OpConst32) => (Add32 i (Add32 <t> z x))
8条答案
按热度按时间goucqfw61#
这对整数可能很好,因为我认为,在Go提供的保证下,加法在Go中是关联的,但对于浮点值,并非总是如此。
rta7y2nd2#
SSA后端针对不同的数据类型有不同的操作,以避免此类问题。(我们要感谢@dr2chase。)同样,这种优化也不适用于添加指针,因为中间结果可能是无效指针。
oipij1gg3#
相关地,这种优化也不适用于添加指针,因为中间结果可能是无效的指针。请注意,将
(ptr + x1) + x2
重写为ptr + (x1 + x2)
是安全的(据我所知),只是不能反过来。尽管如此,我对这种方法在实践中是否有用持怀疑态度。hfsqlsce4#
https://go.dev/cl/493115提到了这个问题:
cmd/compile: add reassociate pass to rebalance commutative operations
5cg8jx4n5#
有趣的观点。据我所知,在其他编译器中的重新关联主要是为了协助其他优化(如sccp、gcse、loopopt)。似乎将重新关联用于“加速CPU超标量执行”是一种新的表达式,是否有相关的扩展信息/论文?也许这对其他编译器也有帮助。谢谢。
Java仅在循环中不变式参与计算时应用重新关联。GCC trunk does not 重新关联
a+b+c+d
到(a+b)+(c+d)
。Clang trunk 进行了重新关联。uoifb46i6#
是否有相关的扩展信息/论文?
@y1yang0 请参阅 https://www.agner.org/optimize/optimizing_assembly.pdf 的第9.5节(以及整章9)
mutmk8jj7#
是的,Clang通过LLVM中的Reassociate pass输出优化。
与LLVM的版本相比,这是一个非常简单的分析传递,其中一些LLVM传递已经被重写规则处理了,直到我在提交PR之后才知道这些规则的存在,所以我进一步简化了这个过程,以消除常量排序。
如果在进一步优化后再次运行此传递,它可能会有常量折叠机会,但在gcse之前完成更重要,因为它可以帮助将表达式很好地分组在一起。目前的目标只是加速无序执行,我会尽快处理这个优化带来的更多机会。
看起来Java只在循环内部进行重新关联优化的原因是自动向量化。这种类型的传递有助于整理所有依赖关系,使您可以轻松识别出4个连续的添加操作,如果模型决定这样做是经济有效的,它们可以转换为一些SIMD。
尽管如此,这个传递似乎在很多地方都有助于简化分析,所以我会研究其他编译器和LLVM,看看我们是否可以在Go中利用它。
58wvjzkj8#
https://go.dev/cl/496095提到了这个问题:
cmd/compile: add ilp pass to help balance commutative expressions aiding in instruction level parallelism