安全的数学优化,同样的公式在不同的行上,不同的结果

funsafe-math-optimizations, same formula on two different lines, different result

本文关键字:结果 优化 安全      更新时间:2023-10-16

我在循环中有以下代码:

while(true)
{
    float i1, i2;
    if(y==0)
    {
        i1 = 0;
    }
    else
    {
        //if y==108, this gives 74.821136 (note the two last digits)
        i1 = ((values[y]+values[y+1])-values[1])*0.5f;
    }
    if(y+2==values.size())
    {
        i2 = values[y+1];
    }
    else
    {
        //if y==107, this gives 74.821129 (note the two last digits)
        i2 = ((values[y+1]+values[y+2])-values[1])*0.5f;
    }
    if(i1<=t && t<i2) {
        break;
    }
    else if(t<i1) {
        y--;
    }
    else {
        y++;
    }
}

计算y=107, t=74.821133

  • i1: 74.773773, i2: 74.821129

And for y=108:

    74.821136
  • i1: , i2: 74.868484

可以看到,当y=107时的i2与y=108时的i1略有不同,而用于计算这两个值的行是相同的。

我知道funsafe-math-optimizations使用代数规则重新组织数学公式,这可能会由于有限的精度而导致数值误差。但在这里,两个等效公式的优化方式似乎有所不同。在这个例子中,会导致一个无限循环(就像这个函数看起来的那样,对于给定的浮点数t, y值i1 <= t <i2)>

这是一个错误的gcc 4.8.0行为吗?

如果我创建一个函数:

float getDifValue(y) const { (values[y]+values[y+1])-values[1])*0.5f; }

然后在循环中使用:

    if(y==0)
    {
        i1 = 0;
    }
    else
    {
        i1 = getDifValue(y);
    }
    if(y+2==values.size())
    {
        i2 = values[y+1];
    }
    else
    {
        i2 = getDifValue(y+1);
    }

我能保证y=107的i2和y=108的i1会产生相同的结果吗?或者编译器可以内联getDifValue并在两个地方对其进行不同的优化?

谢谢

看了反汇编之后,似乎funsafe优化确实改变了

float i1 = ((values[y]+values[y+1])-values[1])*0.5f;
float i2 = ((values[y+1]+values[y+2])-values[1])*0.5f;

为:

float i1 = ((values[y+1]-values[1])+values[y])*0.5f;
float i2 = ((values[y+1]-values[1])+values[y+2])*0.5f;

因为它可以只计算一次(values[y+1]-values[1])

然后,i2 (y==127)和i1 (y==128)现在的计算略有不同,fpu舍入会导致结果不同。

将计算作为y的单独函数解决了这个问题。但是,如果编译器决定内联和优化,这个问题可能会再次出现,这个问题仍然是开放的。

即使x=y; if (x==y) ...也不能保证使用这些优化。例如,它可能会将寄存器中的值与内存中的值进行比较,而内存中的值可能精度较低。

这可能是导致这里问题的原因。在一种情况下,可以使用浮点寄存器中的值,在另一种情况下,没有足够的寄存器,必须将值写入内存然后再读回来。也许i1停留在最后一个可用的寄存器中,但是i2必须进入内存。

也可能是完全不同的东西。但这并不意外。

在我看来,您比较的数字超出了浮点数的机器精度(float为1e-7, double为1e-16)。这意味着你输出的数字比你应该输出的多。如果你输出变量的二进制表示,而不是数值,我猜他们是一样的。如果你担心1e-7不够,我建议你用双打。