使用 IEEE 浮点进行优化 - 保证数学恒等式?

Posted

技术标签:

【中文标题】使用 IEEE 浮点进行优化 - 保证数学恒等式?【英文标题】:optimizing with IEEE floating point - guaranteed mathematical identities? 【发布时间】:2009-12-29 06:16:35 【问题描述】:

我在 IEEE 浮点规则方面遇到了一些问题,这些规则阻止了看起来很明显的编译器优化。例如,

char foo(float x) 
    if (x == x) 
       return 1;
    else 
       return 0; 

不能优化为只返回 1,因为 NaN == NaN 为假。好吧,好吧,我猜。

但是,我想编写这样的代码,优化器实际上可以为我解决问题。是否存在适用于所有浮点数的数学恒等式?例如,如果这意味着编译器可以假设它一直保持不变,我愿意写 !(x - x)。

我在网络上看到了一些对此类身份的引用,例如 here,但我没有找到任何有组织的信息,包括对 IEEE 754 标准的简单扫描。

如果我可以让优化器假设 isnormal(x) 而不生成额外的代码(在 gcc 或 clang 中),那也很好。

显然,我实际上并不打算在我的源代码中编写 (x == x),但我有一个专为内联而设计的函数。该函数可以声明为 foo(float x, float y),但通常 x 为 0,或 y 为 0,或 x 和 y 均为 z 等。浮点数表示屏幕上的几何坐标。在这些情况下,如果我在不使用函数的情况下手动编码,我永远不会区分 0 和 (x - x),我只会手动优化愚蠢的东西。所以,我真的不关心内联函数后编译器执行的 IEEE 规则,我只想让编译器忽略它们。舍入差异也不是很重要,因为我们基本上是在屏幕上绘制。

我不认为 -ffast-math 对我来说是一个选项,因为该函数出现在头文件中,使用该函数的 .c 文件与 -ffast-math 一起编译是不合适的。

【问题讨论】:

【参考方案1】:

另一个可能对您有用的参考资料是 Yossarian King 在 Game Programming Gems 第 2 卷中关于浮点优化的一篇非常好的文章。您可以阅读文章here。它非常详细地讨论了 IEEE 格式,考虑了实现和架构,并提供了许多优化技巧。

【讨论】:

谢谢,我去看看。不过,我仍然对这个身份问题感兴趣..【参考方案2】:

我认为你总是会努力让计算机浮点数算术表现得像数学实数算术,并建议你不要出于任何原因。我建议您在尝试比较 2 个 fp 数字的相等性时出现类型错误。由于 fp 数字绝大多数是近似值,因此您应该接受这一点并使用近似相等作为您的测试。

存在用于数值相等性测试的计算机整数。

好吧,这就是我的想法,如果你愿意,你可以继续与机器(实际上是所有机器)战斗。

现在,回答您的部分问题:

--对于您熟悉的实数算术中的每一个数学恒等式,在浮点数领域都有反例,无论是 IEEE 还是其他;

-- “聪明”的编程几乎总是让编译器比直接编程更难优化代码;

-- 似乎您正在做一些图形编程:最终,您的概念空间中的点坐标将映射到屏幕上的像素;像素始终具有整数坐标;您从概念空间到屏幕空间的转换定义了您的近似相等函数

问候

标记

【讨论】:

我不认为你在关注这个......这是泛型编程的一种形式。我想用对一个过于通用但可读的函数的调用来替换临时转换代码,并让编译器将其优化回与我编写特定版本相同的状态。这更类似于假设有符号整数算术不会溢出,允许编译器进行优化。基本上,你给我的是初学者浮点语音,它不适用。 在我看来,问题在于 IEEE 浮点对编译器的限制惊人。真的不是 C 通常做出的权衡。它在源代码中保留了真正不存在的“细节”,因为如果我手动实例化我的通用模板,我会简单地删除像 (x - x)*complicatedExpression 这样的东西。我可以相信做数字的人需要它,这是一个很好的默认设置,但如果即使是精明的浮点用户也无法传达这种转换是合法的,那就太可惜了。 Fortran 允许这样做。 @Ken:根本问题是 IEEE 试图通过一组比较/关系运算符来解决问题,而不是建议语言应该提供“数据处理”集和“数学”集。关系运算符的 DP 集应该已经定义了等价关系和传递排序 [可能将 NaN 排序在负无穷之下],而数学运算符会特别对待 NaN。几十年后仍然缺乏方便的等价关系测试,这令人难以置信。【参考方案3】:

如果您可以假设此模块中使用的浮点数不是 Inf/NaN,您可以使用 -ffinite-math-only(在 GCC 中)对其进行编译。对于您发布的示例,这可能会“改进”代码生成。

【讨论】:

嘿斯蒂芬!不,我可能也做不到。我现在最好的领导是使用clang,然后放入'if (isnan(f)) builtin_unreachable();'之类的东西。这也不起作用,但它似乎__应该,所以我可以为它提交错误。 看来您真正想要的是一个 assert-as-optimizer-hint 或 #pragma finite_math,这是可能的。 我认为这就是 builtin_unreachable 的用途。它通知优化器某些代码路径不能发生。 是的,这似乎是一种合理(如果不是特别漂亮)的方法。请注意,isnan 是一个宏(在math.h 中定义),因此优化器无法使用builtin_unreachable 进行推理我并不感到惊讶。我会尝试if (x != x) builtin_unreachable(); 那个版本也没有运气。 rdar://problem/7502262【参考方案4】:

您可以比较按位相等。尽管您可能会因某些等效但按位不同的值而被咬,但它会捕获所有您提到的真正相等的情况。而且我不确定编译器是否会识别您所做的并在内联时将其删除(我相信这是您所追求的),但这很容易检查。

【讨论】:

好点,但在编译器无法证明相等的情况下,这会留下比较和分支。这与仅仅简化数学有点不同。我对我现在正在做的事情相当满意:在要内联的函数顶部断言 isfinite(f) 然后提交编译器错误,编译器现在应该能够优化数学。【参考方案5】:

当您以显而易见的方式尝试并对其进行分析时发生了什么?或检查生成的 asm?

如果函数内联了调用站点已知的值,则优化器会提供此信息。例如:foo(0, y)

您可能会对您不必必须做的工作感到惊讶,但至少分析或查看编译器对代码的实际操作将为您提供更多信息并帮助您弄清楚下一步该去哪里。

也就是说,如果您知道优化器自己无法解决的某些事情,您可以编写函数的多个版本,并指定您要调用的那个。这有点麻烦,但至少对于内联函数,它们都将在一个标题中一起指定。它也比下一步要容易得多,即使用内联 asm 来做你想做的事。

【讨论】:

我正在查看生成的代码。在一个简化的示例中,我正在查看该函数的单个调用,我手动编写的版本是 16 条指令,而使用内联函数则为 56 条。我想在热代码中使用该函数,它通常会被多次调用。编写泛型函数并将其内联的目的是使代码更易于阅读和维护。

以上是关于使用 IEEE 浮点进行优化 - 保证数学恒等式?的主要内容,如果未能解决你的问题,请参考以下文章

IEEE 754浮点除法或减法本身是不是总是产生相同的值?

浮点数学是否破碎?

32 位到 16 位浮点转换

IEEE 754 中的指数

从 IBM 浮点转换为 IEEE 浮点标准和副 Versa- 在 C# 中?

无法分离 ieee 754 浮点的不同部分