3

我正在用 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 操作花费更长的时间,并且在从内存加载浮点数之前,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;

}

4

2 回答 2

1

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

float m1 = configParams.p1 * configParams.f14;

鉴于您正在使用优化编译它,并且您没有发布完整的代码,您确定它只是没有相对于您的代码乱序吗?或者,您确定struct定义是正确的吗?您似乎已将其匿名化并缩写,从而使发布的代码与您实际看到的不同。

于 2012-10-02T09:04:42.953 回答
0

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

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

  1. 在进行激进的浮点优化时,它会重新排序浮点运算
  2. 使一些计算以扩展的精度发生

(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当然,如果你需要一致性,你不想要。

于 2012-10-02T10:40:48.840 回答