C++ C++;,浮点算术运算如何得到优化?
在x86体系结构上测试极限情况下的简单算术运算时,我观察到一个令人惊讶的行为:C++ C++;,浮点算术运算如何得到优化?,c++,optimization,floating-point,C++,Optimization,Floating Point,在x86体系结构上测试极限情况下的简单算术运算时,我观察到一个令人惊讶的行为: const double max = 9.9e307; // Near std::numeric_limits<double>::max() const double init[] = { max, max, max }; const valarray<double> myvalarray(init, 3); const double mysum = myvalarray.sum(); co
const double max = 9.9e307; // Near std::numeric_limits<double>::max()
const double init[] = { max, max, max };
const valarray<double> myvalarray(init, 3);
const double mysum = myvalarray.sum();
cout << "Sum is " << mysum << endl; // Sum is 1.#INF
const double myavg1 = mysum/myvalarray.size();
cout << "Average (1) is " << myavg1 << endl; // Average (1) is 1.#INF
const double myavg2 = myvalarray.sum()/myvalarray.size();
cout << "Average (2) is " << myavg2 << endl; // Average (2) is 9.9e+307
(不过,这次gcc将平均值(2)设置为#INF
:)
感谢您的猜测,但是:可能是在浮点寄存器中直接计算平均值(2),该寄存器的宽度为80位,溢出时间晚于64位存储器中的双精度存储器。您应该检查代码的反汇编,看看是否确实如此。在某些情况下,编译器可以使用比声明的类型所隐含的类型更宽的类型,但好吧,这不是其中之一 因此,我认为我们有一个类似的效果,在不应该的情况下使用额外的精度 x86有80位FP内部寄存器。虽然gcc倾向于以最大精度使用它们(因此是bug 323),但我的理解是MSVC将精度设置为53位,即64位的两倍。但延长的显著性并不是80位FP的唯一区别,指数范围也增加了。和IIRC,x86中没有强制使用64位双精度范围的设置 代码板现在似乎不可访问,否则我会在没有80位长双精度码的体系结构上测试您的代码
g++ -O0 -g -S test.cpp -o test.s0
g++ -O3 -g -S test.cpp -o test.s3
比较test.s[03]可以看出,实际上valarray::sum甚至不再被调用。我已经看了很长时间了,但以下片段似乎是定义片段:
.loc 3 16 0 ; test.s0
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE3sumEv
fstpl -96(%ebp)
leal -72(%ebp), %eax
movl %eax, (%esp)
call __ZNKSt8valarrayIdE4sizeEv
movl $0, %edx
pushl %edx
pushl %eax
fildq (%esp)
leal 8(%esp), %esp
fdivrl -96(%ebp)
fstpl -24(%ebp)
.loc 3 17 0
对
.loc 1 16 0 ; test.s3
faddl 16(%eax)
fdivs LC3
fstpl -336(%ebp)
LVL6:
LBB449:
LBB450:
.loc 4 514 0
这是一种特征,或者至少是故意的。 基本上,x86上的浮点寄存器具有更多 精度和范围比双精度(15位指数,而不是 11,64位matissa,而不是52)。C++标准允许 对中间值使用更高的精度和范围,以及 几乎所有英特尔编译器都会在某些情况下这样做 情况;表现上的差异是显著的。 是否获得扩展精度取决于何时 以及编译器是否溢出到内存中。(将结果保存在 命名变量需要编译器将其转换为实际变量 双精度,至少根据标准) 我见过的更糟糕的情况是一些代码基本上做到了:
return p1->average() < p2->average()
返回p1->average()average()
,使用average()
在内部表上执行预期的操作
在数据中。在某些情况下,p1
和p2
实际上是一个关键点
对于相同的元素,但返回值仍然为true;
其中一个函数调用的结果将溢出到
内存(并截断为double
),另一个
保留在浮点寄存器中
(该函数用作排序的排序函数,sort
,
结果代码崩溃了,因为由于这种影响
没有定义足够严格的订购标准
排序
超出传递给它的范围时的代码。)无法访问。init
数组的内容是什么<代码>{9.9e+307,9.9e+307,9.9e+307}?@KennyTM:是的,我忘了我问题中的代码(现在更新)。谢谢。MSVC有一个选项(一致的FP操作)来打开或关闭这个“功能”。事实上,我相信它是允许使用额外精度的地方之一(它在单个语句中,没有显式转换,所有内容都是内联的)。另外,正如引用的错误中所解释的,如果编译器想要充分利用x87 fpu,无论是否应该使用,它都必须使用更高的精度。@Mark B:在阅读引用的错误后,我相信关闭“功能”在这种情况下不会有帮助,因为只有尾数的大小可以减少,而指数的大小不能减少。溢出发生的时间取决于指数。@Mark B:找到了,谢谢你指出。
return p1->average() < p2->average()