为什么不能(或不)编译器将可预测的加法循环优化为乘法?

在阅读Mysticial关于这个问题的杰出答案时,想到这个问题:为什么处理排序后的数组比未排序的数组更快?

涉及类型的上下文:

const unsigned arraySize = 32768;
int data[arraySize];
long long sum = 0;

在他的回答中他解释说,英特尔编译器(ICC)优化了这一点:

for (int i = 0; i < 100000; ++i)
    for (int c = 0; c < arraySize; ++c)
        if (data[c] >= 128)
            sum += data[c];

...变成与此相当的东西:

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

优化器认识到这些是等价的,因此正在交换循环,将分支移到内部循环之外。 非常聪明!

但为什么不这样做呢?

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000 * data[c];

希望Mysticial(或任何其他人)可以给出同样出色的答案。 我从来没有听说过在其他问题中讨论过的优化,所以我对此感到非常感激。


编译器通常不能转换

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000 * data[c];

因为后者可能会导致有符号整数的溢出,前者不会。 即使对于有符号二进制补码整数溢出的保证环绕行为,它也会改变结果(如果data[c]为30000, -1294967296对于典型的具有环绕的32位int ,产品将变为-1294967296 ,而100000次增加30000至sum会,如果不溢出,增加sum由30亿)。 请注意,对于无符号数量也是如此,使用不同的数字, 100000 * data[c]溢出通常会引入模2^32的减法,但不得出现在最终结果中。

它可以转化为

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000LL * data[c];  // resp. 100000ull

尽管如此,如果像往常一样, long longint更大。

为什么不这样做,我说不出来,我猜这就是Mysticial所说的,“显然,它在循环交换之后并没有运行循环崩溃通行证”。

请注意,循环交换本身通常不是有效的(对于有符号整数),因为

for (int c = 0; c < arraySize; ++c)
    if (condition(data[c]))
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

可能导致溢出的地方

for (int i = 0; i < 100000; ++i)
    for (int c = 0; c < arraySize; ++c)
        if (condition(data[c]))
            sum += data[c];

不会。 它在这里是洁净的,因为条件可以确保所添加的所有data[c]具有相同的符号,所以如果溢出的话,两者都可以。

我不太确定编译器是否考虑到了这一点,尽管(@Mysticial,你可以尝试使用像data[c] & 0x80这样的条件,对于正值和负值可以是正确的)。 我有编译器进行无效优化(例如,几年前,我有一个ICC(11.0,iirc)在1.0/n中使用signed-32-bit-int-to-double转换,其中nunsigned int 。大约是海湾合作委员会产量的两倍,但错误的是,很多值大于2^31 ,oops)。


此答案不适用于链接的特定案例,但它适用于问题标题,可能会对未来的读者感兴趣:

由于精度有限,重复的浮点加法不等于乘法 。 考虑:

float const step = 1e-15;
float const init = 1;
long int const count = 1000000000;

float result1 = init;
for( int i = 0; i < count; ++i ) result1 += step;

float result2 = init;
result2 += step * count;

cout << (result1 - result2);

演示:http://ideone.com/7RhfP


编译器包含进行优化的各种通道。 通常在每次传递中,优化语句或循环优化都已完成。 目前还没有模型基于循环头进行循环体的优化。 这很难发现,也不太常见。

所做的优化是循环不变码运动。 这可以使用一套技术来完成。

链接地址: http://www.djcxy.com/p/29.html

上一篇: Why can't (or doesn't) the compiler optimize a predictable addition loop into a multiplication?

下一篇: How do I check if an element is hidden in jQuery?