关于浮点:避免使用C ++中的非正规值

Avoiding denormal values in C++

在搜索了很长一段时间的性能错误之后,我读到了关于非正规浮点值的内容。

显然,非规范化的浮点值可能是主要的性能问题,如本问题所示:为什么将0.1f更改为0会将性能降低10倍?

我有一个英特尔酷睿2二重奏,我正在用gcc编译,使用-O2

那我该怎么办?我能指示g++避免非正规值吗?如果不是,我可以用某种方法测试一个float是否异常?


等待。在您做任何事情之前,您是否知道您的代码遇到了非正常值,并且它们对性能有可测量的影响?

假设您知道这一点,那么如果关闭非规范支持,您是否知道正在使用的算法是否稳定?更快地得到10倍的错误答案通常不是一个好的性能优化。

撇开这些问题不谈:

  • 如果您想检测非正常值以确认它们的存在,您有几个选项。如果您有C99标准库或Boost,则可以使用fpclassify宏。或者,您可以将数据的绝对值与最小的正态数进行比较。

  • 您可以将硬件设置为将非规范值刷新为零(ftz),或者将非规范输入视为零(daz)。如果在您的平台上得到适当的支持,最简单的方法可能是在c header fenv.h中使用fesetenv( )函数。然而,这是C标准最不受广泛支持的特性之一,并且本质上是特定于平台的。您可能只需要使用一些内联程序集直接将fpu状态设置为(daz/ftz)。


您可以使用

1
2
3
#include <cmath>

if ( std::fpclassify( flt ) == FP_SUBNORMAL )

(警告:我不确定这是否会在实践中全速执行。)

在C++ 03中,这个代码在我的实践中起作用,

1
2
3
4
5
6
#include <cmath>
#include <limits>

if ( flt != 0 && std::fabsf( flt ) < std::numeric_limits<float>::min() ) {
    // it's denormalized
}

要决定在何处应用,可以使用基于示例的分析器(如shark、vtune或zoom)来突出显示因非正常值而减慢的指令。微观优化,甚至比其他优化,在没有分析之前和之后是完全没有希望的。


大多数数学协处理器都有将非正规值截断为零的选项。在x86上,它是mxcsr控制寄存器中的fz(flush to zero)标志。检查CRT实现中是否有设置控制寄存器的支持函数。它应该在中,类似于controlfp()。选项位通常在定义的符号中有"flush"。

设置此项后,再次检查数学结果。不管怎样,这是你应该做的,非正常化是健康问题的征兆。


在GCC中具有(flush to zero)ftz(假设下溢被默认屏蔽):

1
2
3
4
#define CSR_FLUSH_TO_ZERO         (1 << 15)
unsigned csr = __builtin_ia32_stmxcsr();
csr |= CSR_FLUSH_TO_ZERO;
__builtin_ia32_ldmxcsr(csr);

如果从名称上看不明显,那么只有当您的目标是x86处理器时,__builtin_ia32_stmxcsr__builtin_ia32_ldmxcsr才可用。ARM、SPARC、MIPS等都需要使用此方法的单独平台特定代码。


正如对其他答案的补充一样,如果您确实有非正规浮点值的问题,那么除了性能问题之外,您可能还有精度问题。

检查是否可以重新构造计算以使数字更大以避免损失精度和性能,这可能是一个好主意。


显然,您需要一些名为ftz(flush to zero)和daz(nonormals are zero)的CPU指令。

我在一个音频网站上找到了信息,但是他们与Intel文档的链接丢失了。它们显然是SSE2指令,因此它们应该在支持该指令的AMD CPU上工作。

我不知道你能在GCC中做些什么,以一种便携的方式强制执行它。但是,您始终可以编写内联程序集代码来使用它们。您可能需要强制GCC只使用SSE2进行浮点运算。