提问人:Kyle Ponikiewski 提问时间:11/19/2022 最后编辑:Eric PostpischilKyle Ponikiewski 更新时间:11/22/2022 访问量:120
展开的 for 循环之间的程序集差异会导致不同的浮点结果
Assembly differences between unrolled for-loops cause differing float results
问:
请考虑以下设置:
typedef struct
{
float d;
} InnerStruct;
typedef struct
{
InnerStruct **c;
} OuterStruct;
float TestFunc(OuterStruct *b)
{
float a = 0.0f;
for (int i = 0; i < 8; i++)
a += b->c[i]->d;
return a;
}
TestFunc 中的 for 循环在我正在测试的另一个函数中完全复制了一个循环。 两个循环都由 gcc (4.9.2) 展开,但这样做后产生的汇编略有不同。
我的测试循环的组装:ᅠᅠᅠᅠᅠᅠᅠ原始循环的组装:
lwz r9,-0x725C(r13) lwz r9,0x4(r3)
lwz r8,0x4(r9) lwz r8,0x8(r9)
lwz r10,0x0(r9) lwz r10,0x4(r9)
lwz r11,0x8(r9) lwz r11,0x0C(r9)
lwz r4,0x4(r8) lwz r3,0x4(r8)
lwz r10,0x4(r10) lwz r10,0x4(r10)
lwz r8,0x4(r11) lwz r0,0x4(r11)
lwz r11,0x0C(r9) lwz r11,0x10(r9)
efsadd r4,r4,r10 efsadd r3,r3,r10
lwz r10,0x10(r9) lwz r8,0x14(r9)
lwz r7,0x4(r11) lwz r10,0x4(r11)
lwz r11,0x14(r9) lwz r11,0x18(r9)
efsadd r4,r4,r8 efsadd r3,r3,r0
lwz r8,0x4(r10) lwz r0,0x4(r8)
lwz r10,0x4(r11) lwz r8,0x0(r9)
lwz r11,0x18(r9) lwz r11,0x4(r11)
efsadd r4,r4,r7 efsadd r3,r3,r10
lwz r9,0x1C(r9) lwz r10,0x1C(r9)
lwz r11,0x4(r11) lwz r9,0x4(r8)
lwz r9,0x4(r9) efsadd r3,r3,r0
efsadd r4,r4,r8 lwz r0,0x4(r10)
efsadd r4,r4,r10 efsadd r3,r3,r11
efsadd r4,r4,r11 efsadd r3,r3,r9
efsadd r4,r4,r9 efsadd r3,r3,r0
问题是这些指令返回的浮点值并不完全相同。而且我无法更改原始循环。我需要以某种方式修改测试循环以返回相同的值。我相信测试的组装相当于一个接一个地添加每个元素。我对汇编不是很熟悉,所以我不确定上述差异是如何转化为 c 的。我知道这是问题所在,因为如果我将打印添加到循环中,它们不会展开,并且结果与预期完全匹配。
答:
我认为这是为了对一个函数与另一个函数进行单元测试。
一般来说,浮点计算在 C 或 C++ 中从来都不是精确的,通常不被认为是合法的。
Java 语言标准需要精确的浮点结果。这样做一直是对 Java 的仇恨的根源,有各种指责认为,使结果可重复通常会使它们不那么准确,有时也会使代码变慢。
如果您正在用 C 或 C++ 进行测试,那么我建议采用这种方法:
尽可能精确地计算结果,同时具有高精度和高精度。在这种情况下,输入数据采用 32 位浮点数,因此在计算预期结果之前,将它们全部转换为 64 位浮点数。
如果输入是双精度的(并且您没有更大的长双精度类型),则按顺序对值进行排序,并将它们从小到大相加。这将导致最小的准确性损失。
获得预期结果后,请测试函数输出是否在某个范围内匹配。
有两种方法可以设置将测试视为通过所需的精度:
一种方法是检查数字的真正物理含义是什么,以及您实际需要的准确性。
另一种方法是只要求结果精确到理想结果的几个最低有效位以内,即:误差小于理想结果乘FLT_EPSILON的几倍。
评论
禁用快速数学似乎可以解决这个问题。感谢@njuffa的建议。我希望能够围绕这种优化设计测试功能,但这似乎是不可能的。至少我知道现在的问题是什么。感谢大家对这个问题的帮助!
评论
c[i]
是一个指针,所以不应该编译。请提供一个最小的可重复示例。b->c[i].d
efsadd
c[0].d + c[1].d + c[2].d + c[3].d + c[4].d + c[5].d + c[6].d + c[7].d
c[1].d + c[2].d + c[3].d + c[4].d + c[5].d + c[6].d + c[0].d + c[7].d
-fp-model:strict