浮点运算优化导致Visual C ++出现罕见和奇怪的行为

时间:2012-10-02 07:41:06

标签: c++ visual-c++ optimization

我在用C ++编写的例程中使用Visual C ++ 2008编译了几个浮点操作。我也激活了优化(/ O2)。

C ++中的代码大致如下:

int Calculate( CalculationParams &params )
{
    const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();

    float m1 = configParams.p1 * configParams.p2;
    float m2 = configParams.p3 * configParams.p4;
    float m3 = configParams.p5 * configParams.p6;

    ....
}

ConfigReader是一个包含用于计算的参数结构的单例。这是由configParams引用引用的。

激活优化后,在极少数情况下,我会得到计算错误,结果完全错误。

看看反汇编我看到了:

int Calculate( CalculationParams &params )
{
    ...
    const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();
        call ConfigReader::Instance()
        move ebx, eax

    float m1 = configParams.p1 * configParams.p2;
        fld dword ptr[ebx + 0D4h]
        add ebx, 8
        fmul dword ptr [ebx + 0ECh]
    float m2 = configParams.p3 * configParams.p2;
    float m3 = configParams.p4 * configParams.p2;
    ...
}

首先,我们看到它不会调用Parameters()。这是可以理解的,因为参数结构位于类中,8个字节(在两个其他浮点之后)。因此,在调用之后,eax具有ConfigReader CLASS的地址(而不是ConfigurationParams结构)。

然后它尝试加载浮动。这是问题发生的地方。由于某种原因,对于指向ConfigReader类的ebx,加载操作的偏移量不正确。它应首先添加8,以使偏移正确。

编译器是否可能假定fld操作将花费比add操作更长的时间,并且在从内存加载float之前,ebx会以某种方式添加8?这可以吗?我们的偶然问题可能源于此时发生的中断,并导致ebx在浮点数加载时没有8的偏移量吗?

我希望这种方法的唯一方法是将添加操作放在fld之前。很难理解这根本有用......

有没有办法关闭这种重排优化?

编辑: ConfigReader看起来像这样

class ConfigReader
{
public:
    static ConfigReader& Instance();

    const ConfigurationParams& Parameters() const { return myParameters; }

private:
    ConfigReader();

    float internalParam1;
    float internalParam2;

    ConfigurationParams myParameters;
}



struct ConfigurationParams
{
    char s1[10];
    char s2[50];
    int i1;
    int i2;
    int i3;
    int i4;
    int i5;
    int i6;
    int i7;
    int i8;
    int i9;
    int i10;
    int i11;
    int i12;
    int i13;
    int i14;
    int i15;
    int i16;
    int i17;
    int i18;
    int i19;
    int i20;
    int i21;
    int i22;
    int i23;

    float f1;
    float f2;

    int i25;
    int i26;

    int i27;
    int i28;
    int i29;

    int i30;
    int i31;

    bool b1;

    float f3;
    float f4;
    float f5;
    float p1;
    float p3;
    float p4;
    float f9;
    float f10;
    float f11;
    float f12;
    float p2;
    float f14;
    float f15;
    float f16;

    int i32;

    int i33;
    int i34;
    int i35;

}

2 个答案:

答案 0 :(得分:1)

实际上,fld对我来说是正确的。它是fmul看起来像是错误的价值。生成的代码正在执行:

float m1 = configParams.p1 * configParams.f14;

鉴于您正在使用优化进行编译,并且您没有发布完整的代码,您确定它不会相对于您的代码执行无序的操作吗?或者,您确定struct定义是正确的吗?看起来你已经匿名化并缩写了这一点,从而使发布的代码与你实际看到的不同。

答案 1 :(得分:0)

我简直不敢相信编译器生成的变量地址存在错误。这不是不可能,但非常罕见。这必须首先排除,但看起来我们没有提供所有相关的代码来判断。

然而,最可能发生的是编译器正在执行以下一项或两项操作:

  1. 在进行积极的浮点优化时reorders the floating point operations
  2. makes some computations happen with extended precision
  3. (1)的结果是,尽管从数学角度来看优化是正确的,但数学公理在精度有限的计算中停止工作,这就是为什么任意(在编译器的奇思妙想)重新排序导致结果与您可能期望或相对于相同代码的未优化版本中的结果。

    事实上,在大多数计算机中,浮点运算的工作原理如下:

    (a + b)+ c≠a +(b + c)
    (a * b)* c≠a *(b * c)
    (a + b)* c≠(a * c)+(b * c)
    ......等等(参见Knuth的TAOCP或已经提到的Microsoft Visual C++ Floating-Point Optimization)。

    因此,浮点运算的重新排序通常不利于一致性。

    根据C标准允许的(2)的结果是,您可以在不同优化的代码中获得具有不同精度的中间结果。预计最终结果也会有所不同。

    我要做的是先尝试/fp:strict,如果正在使用/fp:precise(这是默认设置)。当然,如果你需要一致性,你不需要/fp:fast