C中“双重”和优化的操作

Posted

技术标签:

【中文标题】C中“双重”和优化的操作【英文标题】:Operations on "double" and optimization in C 【发布时间】:2015-01-07 21:29:41 【问题描述】:

我最近分析了一段用 VS2005 编译的旧代码,因为“调试”(无优化)和“发布”(/O2 /Oi /Ot 选项)编译中的数值行为不同。 (简化的)代码如下所示:

void f(double x1, double y1, double x2, double y2)

double a1, a2, d;

a1 = atan2(y1,x1);
a2 = atan2(y2,x2);
d = a1 - a2;
if (d == 0.0)  // NOTE: I know that == on reals is "evil"!
   printf("EQUAL!\n");

如果使用相同的值对(例如f(1,2,1,2))调用函数f,预计会打印“EQUAL”,但这并不总是发生在“发布”中。事实上,编译器已经像d = a1-atan2(y2,x2) 一样优化了代码,并完全删除了对中间变量a2 的赋值。此外,它利用了第二个atan2() 的结果已经在FPU 堆栈上这一事实,因此在FPU 上重新加载a1 并减去这些值。问题是 FPU 以扩展精度(80 位)工作,而 a1 是“仅”双精度(64 位),因此将第一个 atan2() 的结果保存在内存中实际上已经失去了精度。最终,d 包含扩展精度和双精度之间的“转换错误”。

我完全知道应该避免使用 float/double 的身份(== 运算符)。我的问题不是关于如何检查双打之间的接近度。我的问题是应该如何考虑对局部变量的“合同”分配。从我的“幼稚”观点来看,赋值应该强制编译器将值转换为变量类型表示的精度(在我的例子中是双精度)。如果变量是“浮动的”怎么办?如果它们是“int”(奇怪但合法)怎么办?

那么,简而言之,C 标准对这些情况有什么看法?

【问题讨论】:

我认为该标准没有承诺库中包含的函数的浮点返回。但这是我对这个主题的非专业理解,所以不是很有帮助。 默认情况下,Visual Studio 将精度设置为“精确”,这允许它进行此类优化。您可以尝试将其设置为严格,看看会发生什么。 如果一切都失败了,将所有中间体转换为 (double) 应该可以解决问题。至少当我试图为一个非常具体的操作获取特定于 IEEE 的行为并且我不想打开全局 fp:strict 时,它至少有效。 如果您要摆脱 x87 的超额精度,通常会有一个特定的编译标志;在 gcc 上有 -fexcess-precision=standard(和 -ffloat-store),用于 VC++ /fp:strict @chux VS 2005 甚至没有声称要实现 C99,因此它不必定义 FLT_EVAL_METHOD。而且编译器在定义它时并不总是按照他们所说的去做(***.com/questions/17663780/…) 【参考方案1】:

从我的“幼稚”观点来看,赋值应该强制编译器将一个值转换为变量类型所表示的精度(在我的例子中是双精度)。

是的,这就是 C99 标准所说的。见下文。

那么,简而言之,C 标准对这些情况有什么看法?

在某些情况下,C99 标准允许以比类型所暗示的精度更高的精度计算浮点运算:在 standard 中查找 FLT_EVAL_METHODFP_CONTRACT,这是两个构造与精度过高有关。但是我不知道任何可以解释为允许编译器将浮点值的精度从计算精度任意降低到类型精度的词。按照对标准的严格解释,这应该只发生在特定的位置,例如分配和演员表,以一种确定的方式。

最好是阅读Joseph S. Myers's analysis与FLT_EVAL_METHOD相关的部分:

C99 允许在超范围和精度下进行评估 某些规则。这些在 5.2.4.2.2 第 8 段中进行了概述:

除了赋值和强制转换(删除所有额外的范围和 精度),具有浮动操作数的操作的值和 值受通常的算术转换和浮动 常量被评估为一种格式,其范围和精度可能 大于该类型的要求。使用评价 格式的特点是实现定义的值 FLT_EVAL_METHOD:

Joseph S. Myers 继续描述了在他的帖子附带的补丁之前 GCC 的情况。情况与您的编译器(以及无数其他编译器)一样糟糕:

GCC 在使用 x87 浮点时将 FLT_EVAL_METHOD 定义为 2。它的 然而,实施不符合 C99 的要求 FLT_EVAL_METHOD == 2,因为是后端实现的 假装处理器支持 SFmode 上的操作,并且 DF模式:

有时,根据优化,一个值可能会溢出到 SFmode 或 DFmode 中的内存,因此会意外丢失过多的精度 并且在 C99 指定丢失时以外的其他地方。 赋值通常不会丢失过多的精度,尽管 -ffloat-store 可能会增加它的可能性。

C++标准从C99继承math.h的定义,math.h是定义FLT_EVAL_METHOD的头部。出于这个原因,您可能希望 C++ 编译器效仿,但他们似乎并没有认真对待这个问题。即使 G++ 仍然不支持-fexcess-precision=standard,尽管它使用与 GCC 相同的后端(自 Joseph S. Myers 的帖子和随附的补丁以来,它一直支持此选项)。

【讨论】:

以上是关于C中“双重”和优化的操作的主要内容,如果未能解决你的问题,请参考以下文章

双重检查锁定与延迟优化

POST 期间的双重等待操作

单例模式的优化-双重锁定

单例模式的优化-双重锁定

Python 优化 - 我可以避免双重 for 循环吗?

关于并发场景下,通过双重检查锁实现延迟初始化的优化问题隐患的记录