8

很容易看出:

(i % 3 == 0) && (i % 5 == 0)

可以简化为:

(i % 15 == 0)

然而,查看 GCC 的输出,似乎即使在高优化级别上也没有这样做。

是否有任何编译器进行此类优化,或者是否有充分的理由说明这两个测试在语义上不等效?

编辑:针对那些说这是一个边缘案例的人,以下是一个类似的案例:

(i < 3) && (i < 5)

任何小于 3 的数字,必须始终小于 5。第二次测试是多余的。

我还想添加以下内容以响应编译器无法知道环境是否受到影响的答案...看这段代码:

void foo(void)
{
    int i;
    for (i = 0; i <= 10; i++)
    {
        if (i > 20)
        {
            puts("Hi");
        }
    }
}

GCC 将整个函数简化为“repz ret” -O2。这比我所说的要复杂得多。

4

3 回答 3

5

忽略所有声称这对于编译器来说非常困难/不可能的愚蠢答案。我看不出它为什么会很困难,但大概没有人想过这样做,或者认为优化它足够重要。如果您想要比这更好的答案,您需要在 GCC 错误跟踪器上报告它作为增强请求,并查看开发人员的意见。

于 2012-04-18T19:43:45.297 回答
3

您的示例相当简单,并且确实很容易浓缩为单个操作。然而,像这样的陈述的一般情况并不那么简单。以以下为例:

((++i) % 3 == 0) && ((++i) % 5 == 0)

这种变化不能简单地简化为单个模数运算(我知道这个语句还有各种其他问题,但关键是当您使用除简单变量之外的任何东西时,问题并不那么简单参考)。编译器通常不会看到您的案例仅涉及简单的变量,并且与一般案例不同地对其进行优化;他们尽可能保持优化的一致性和可预测性。

更新: 您添加到问题中的额外案例属于与原始代码片段完全不同的优化类别。它们都被优化掉了,因为它们是不可访问的代码,并且可以在编译时被证明。您最初的问题涉及将两个操作重写为一个与原始操作不同的等效操作。您添加的两个片段不会重写现有代码,它们只会删除永远无法执行的代码。编译器通常非常擅长识别和删除死代码。

您正在寻求的优化是数学强度降低的一种形式. 大多数编译器都提供一定程度的 MSR 优化,尽管它们得到的详细程度会因编译器和目标平台的功能而异。例如,针对没有模数指令的 CPU 架构的编译器(意味着必须使用可能很长的库函数)可能会更积极地优化此类语句。如果目标 CPU 具有硬件模数支持,编译器编写者可能会认为两个或三个保存的指令的好处太小,不值得花时间和精力来实现和测试优化改进。我在过去看到过这种情况,某些操作可以在 x86 CPU 上的一条指令中完成(例如),但在 RISC CPU 上需要数十条指令。

于 2012-04-18T19:15:31.593 回答
1

老实说,这是一个非常具体的案例。如果方程的任何部分发生变化,优化将不再适用。我认为这是一个成本与收益的问题,实施这种优化的成本(增加编译时间,增加维护难度)超过了收益(在极少数情况下一些不太快的操作)。

一般来说,由于精度限制和溢出的可能性,无法应用数学重构。虽然我认为这不是问题。

于 2012-04-18T19:17:55.463 回答