为什么常数有理数乘法没有被优化?

12

由于有符号整数溢出是未定义行为,我预计下面的三个函数将编译为相同或类似的汇编代码。然而事实并非如此。test2test1稍有不同,test3使用了两个不必要的imul指令。

int test1(int x)
{
    return x * 5 / 2;
}

int test2(int x)
{
    return x * 10 / 4;
}

int test3(int x)
{
    return x * 50 / 20;
}

在编译器资源管理器上的比较

为什么编译器不执行这种优化呢?


4
我认为正式的C++规范本身并没有阻止这种优化。我认为问题就在于“之前没有人想到过”,即可以尝试微调整一个整数乘法后跟除法的序列,找到一些数学上相同的序列,其乘法和除法步骤可以通过优化的机器语言指令完成。 - Sam Varshavchik
2
如果两个因素抵消为整数值,则会执行您期望的优化。我不确定四舍五入除法是否会有不同的行为,或者编译器是否无法证明它不会有不同的行为。 - Quentin
10
我怀疑这与GCC支持有符号整数溢出包裹有关。这不是C标准所要求的,但在GCC文档中提到。当存在溢出时,x * 5 / 2x * 10 / 4x * 50 / 20会产生不同的结果,因此它们不能被优化为相同的代码。 - Eric Postpischil
2
@WeatherVane 这并不一定是为了避免溢出。如果GCC具有明确定义的有符号溢出,那么它必须确保它始终表现得好像发生了溢出。 - Thomas Jager
3
欢迎提交补丁。只有因为有人的动力实现了它们,所以优化才只在编译器中进行。 - Marc Glisse
显示剩余8条评论
1个回答

1

这样的优化是否正确取决于实现对整数溢出效果的任何保证:

  1. 如果实现保证整数加法和乘法始终表现为使用足以容纳结果的值进行操作,然后在每次操作后截断为类型大小的补码,则将 x*(m*a)/(m*b) 替换为 x*a/b 将违反此类保证。
  2. 如果实现保证整数加法和乘法始终表现为产生某个数字,但不保证临时值被截断为任何特定大小,则此类优化是有效的。
  3. 如果实现对溢出效果没有任何保证,并要求程序员不惜一切代价避免它们,因为它们可能会导致程序中即使不使用结果的部分出现错误行为,则此类优化是有效的。
gcc编译器提供选项,可以选择支持强保证选项#1,或者完全不提供任何保证,并且整数溢出的可能性会破坏不使用结果的代码的部分,这不仅仅是理论上的。由于选项#3对于可能接收来自不可信来源的输入的程序来说非常危险,而且gcc没有提供介于#1和#3之间的设置,因此许多程序(包括那些选项#2可以满足要求的程序)都使用fwrapv标志构建,该标志强制使用选项#1。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接