这是最优解,使用AND需要至少两个额外的指令,可能需要停止并等待值屏蔽发生的加载。 因此,在某些方面更糟糕。
00000000 <swap>:
0: e1a03420 lsr r3, r0, #8
4: e1830400 orr r0, r3, r0, lsl #8
8: e1a00800 lsl r0, r0, #16
c: e1a00820 lsr r0, r0, #16
10: e12fff1e bx lr
00000000 <swap>:
0: ba40 rev16 r0, r0
2: b280 uxth r0, r0
4: 4770 bx lr
后者是armv7,但同时也是因为他们添加了支持这种工作的指令。
固定长度的RISC指令在常量方面有一个问题。MIPS选择了一种方式,ARM选择了另一种方式。常量在CISC上也是一个问题,只是不同的问题。很容易创建一个利用ARMS桶移位器的东西,并显示MIPS解决方案的缺点,反之亦然。
解决方案实际上有一些优雅之处。
其中一部分也是目标的整体设计。
unsigned short fun ( unsigned short x )
{
return(x+1);
}
0000000000000010 <fun>:
10: 8d 47 01 lea 0x1(%rdi),%eax
13: c3 retq
gcc选择不返回您请求的16位变量,它返回32位,它没有正确实现我用代码请求的函数。但是,如果当数据的用户获取该结果或使用它时发生掩码,或者在此体系结构中使用ax而不是eax,那么这是可以的。
unsigned short fun ( unsigned short x )
{
return(x+1);
}
unsigned int fun2 ( unsigned short x )
{
return(fun(x));
}
0000000000000010 <fun>:
10: 8d 47 01 lea 0x1(
13: c3 retq
0000000000000020 <fun2>:
20: 8d 47 01 lea 0x1(
23: 0f b7 c0 movzwl
26: c3 retq
编译器设计选择(可能基于架构),而不是实现错误。
请注意,对于足够大的项目,很容易找到错过的优化机会。不要指望优化器完美(它不是也不能)。它们只需要比手动操作更高效,以平均每个大小的项目为基础。
这就是为什么通常说,在性能调整中,您不会预先优化或立即跳转到汇编语言,而是使用高级语言和编译器,通过某种方式进行分析以查找性能问题,然后手动编写代码。为什么手动编码?因为我们知道有时可以超越编译器,意味着编译器输出可以得到改进。
这不是错过的优化机会,而是指令集的非常优雅的解决方案。屏蔽字节更简单。
unsigned char fun ( unsigned char x )
{
return((x<<4)|(x>>4))
}
00000000 <fun>:
0: e1a03220 lsr r3, r0, #4
4: e1830200 orr r0, r3, r0, lsl #4
8: e20000ff and r0, r0, #255
c: e12fff1e bx lr
00000000 <fun>:
0: e1a03220 lsr r3, r0, #4
4: e1830200 orr r0, r3, r0, lsl #4
8: e6ef0070 uxtb r0, r0
c: e12fff1e bx lr
后者是armv7,但在armv7中,他们认识到并解决了这些问题。你不能指望程序员总是使用自然大小的变量,有些人觉得需要使用不太优化的变量。有时仍然需要将其掩码为特定大小。
REV16 r0,r0
。 - fuz-march=armv6
,gcc将使用uxth r0, r0
(https://godbolt.org/g/P3ASv3)将其截断为16位,以防调用者在`r0`中留下高垃圾。因此,ARMv6也回答了实际的问题。 - Peter Cordes