【发布时间】:2020-10-09 21:30:23
【问题描述】:
当 ARM gcc 9.2.1 给出命令行选项-O3 -xc++ -mcpu=cortex-m0 [compile as C++] 和以下代码时:
unsigned short adjust(unsigned short *p)
{
unsigned short temp = *p;
temp -= temp>>15;
return temp;
}
它产生合理的机器码:
ldrh r0, [r0]
lsrs r3, r0, #15
subs r0, r0, r3
uxth r0, r0
bx lr
相当于:
unsigned short adjust(unsigned short *p)
{
unsigned r0,r3;
r0 = *p;
r3 = temp >> 15;
r0 -= r3;
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return r0; // computing a 32-bit unsigned value 0-65535.
}
非常合理。在这种特殊情况下,实际上可以省略最后一个“uxtw”,但对于无法证明此类优化的安全性的编译器,谨慎起见比冒险返回 0-65535 范围之外的值更好,这可以完全下沉下游代码。
但是,当使用-O3 -xc -mcpu=cortex-m0 [相同选项,除了编译为 C 而不是 C++] 时,代码会发生变化:
ldrh r3, [r0]
movs r2, #0
ldrsh r0, [r0, r2]
asrs r0, r0, #15
adds r0, r0, r3
uxth r0, r0
bx lr
unsigned short adjust(unsigned short *p)
{
unsigned r0,r2,r3;
r3 = *p;
r2 = 0;
r0 = ((unsigned short*)p)[r2];
r0 = ((int)r0) >> 15; // Effectively computes -((*p)>>15) with redundant load
r0 += r3
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return temp; // computing a 32-bit unsigned value 0-65535.
}
我知道左移定义的极端情况在 C 和 C++ 中是不同的,但我认为右移是相同的。右移在 C 和 C++ 中的工作方式有什么不同会导致编译器使用不同的代码来处理它们吗? 9.2.1 之前的版本在 C 模式下生成的错误代码略少:
ldrh r3, [r0]
sxth r0, r3
asrs r0, r0, #15
adds r0, r0, r3
uxth r0, r0
bx lr
相当于:
unsigned short adjust(unsigned short *p)
{
unsigned r0,r3;
r3 = *p;
r0 = (short)r3;
r0 = ((int)r0) >> 15; // Effectively computes -(temp>>15)
r0 += r3
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return temp; // computing a 32-bit unsigned value 0-65535.
}
没有 9.2.1 版本那么糟糕,但仍然比直接翻译代码更长的指令。使用 9.2.1 时,将参数声明为 unsigned short volatile *p 将消除 p 的冗余负载,但我很好奇为什么 gcc 9.2.1 需要一个 volatile 限定符来帮助它避免冗余负载,或者为什么这种奇怪的“优化”只发生在 C 模式而不是 C++ 模式。我也有点好奇为什么 gcc 甚至会考虑添加((short)temp) >> 15 而不是减去temp >> 15。优化中是否有某个阶段似乎有意义?
【问题讨论】:
-
我发现 C 代码添加移位值而不是按照源代码的意图减去它真的很奇怪。
-
@MarkRansom:在优化版本中执行移位的方式会产生 0 或 -1 而不是 0 或 1,因此将减法更改为加法是正确性所必需的。与我发现的大多数 gcc 怪癖不同,这个怪癖仅产生比直接翻译慢的代码,而不是产生损坏的代码。尽管添加
*p的冗余负载会带来不必要的出错机会,但在某些情况下,如果冗余负载在语义上是可接受的,它们可以提高代码效率。不过,这似乎不是其中之一。 -
今天早上我的大脑一定是异常的慢,我没有想到将一个 16 位的数量移动 15 位只会导致两种结果。
-
@supercat:不完全确定原因,但如果将
temp更改为unsigned int,编译器会生成相同的“合理机器码”。 -
您可以尝试一些
-fopt-infooptions 来更深入地了解优化器正在做什么而不是推测。
标签: c++ c gcc optimization compiler-optimization