在阅读(太多)生成的汇编代码时,我发现了一些看似次优的x86代码序列。我不记得在哪里找到了它们中的每一个,其中一些可能只是来自我的想象,编译器优化指南或来自标准库之外。
这里没有为每个可能性创建一个问题,而是提供了一些可能的低级性能改进列表。请注意,这并不意味着它们是常见的,因此值得引入。可以进行评估。然而,这些可以激发其他改进的想法,并让新的编译器贡献者尝试添加ssa优化规则或代码生成改进,并对其效果和频率进行基准测试。更新:CLs应该确保在引入优化时包含std库中的统计/示例数据。
当前的汇编gc和gccgo创建可以使用https://godbolt.org/快速检查。由于其低级性质,可能会忽略某些可能性以及它们是否是大小或性能改进。如有需要,请进行基准测试和测试。
其中许多都应该被视为更一般优化的例子。
列表:
- 无基础的lea:
没有基址的lea,只有index*operandsize
,导致x+x+x+x
被编译成leaq+addq
,而不是单个leaq[0+x*4]
,通过一些更多的组合规则实现。 - 过多的imul
x*x*x*x
被编译成3个imuls
,而2个就足够了。 - 将寄存器中的所有位设置为1(更新:通常不值得这样做,因为会产生错误的依赖关系)
不要使用MOVQ $-0x1, Reg
,而是使用ORQ $-0x1, Reg
,对于64位整数来说,它短了约4字节,但可能会产生如@randall77所指出的错误依赖关系。 - 比较模数
x % 2 == 0
可以是andl $1, %eax, testq %rax, %rax
(或btl $0, AX
...),而不是shift+shift+add+shift+shift+cmp
。 - 对于2的幂次方的一些情况,用减法代替加法
sub -128
比add 128
短。(注意标志不要使用) - 对于小于等于2的幂次方的情况,差值比位移更好
x < 128
编码为CMPQ $0x80, AX; JG
,大于CMPQ $0x7f, AX; JGE
。对于其他常量的比较编码,应该对其他情况进行类似的操作。 - 用有符号整数除以int
如果已知int除数为正数,而不是使用CQO+IDIV
,可以使用XOR+DIV
。 - 用移位优化模数运算
var x,y uint; x % (1<<y)
可以用x & ((1<<y)-1)
替换。
6条答案
按热度按时间ddarikpa1#
Instead of MOVQ $-0x1, Reg use ORQ $-0x1, Reg which is shorter.
Does this have a false dependency on the previous value of Reg?
XORL AX, AX
is a special case which breaks the dependency, not sure whetherORQ $-1, AX
also does.tktrz96b2#
感谢您的反馈。您是对的,ORQ可能会引入不必要的依赖(或者至少我们不知道所有的x86都会优化掉这个依赖)。除非只优化大小,否则这可能并不总是一个赢家,而且需要进行太多复杂的分析才能确保在具体的指令流中是一个赢家。
已将此反馈添加到列表中。
prdp8dxp3#
与此相关的:#21439。
ktca8awb4#
"shorter"是否与运行时性能有关?我认为较小的代码通常会提高缓存性能,在其他条件相同的情况下。
fbcarpbf5#
"shorter"是否与运行时性能有关?
当然,这些都很难衡量,尤其是在微基准测试中。更小的二进制文件意味着更少的缓存使用。根据指令和微架构,指令解码器每周期也可以解码更多的短/简单指令,某些架构似乎“仅”每周期获取16/32字节的指令。如果字节更小,更多的指令将适合放入循环缓冲区,因此更多的循环可以利用循环缓冲区并因此可能执行得更快。
kyks70gy6#
https://golang.org/cl/149537提到了这个问题:
cmd/compile/internal/ssa: optimized x*x*x*x to only 2 imuls