为何将 0.1f 改为 0 会使性能降低 10 倍?
为何将 0.1f 改为 0 会使性能降低 10 倍?
技术背景
在进行浮点运算时,有时会遇到性能急剧下降的情况。比如在某些代码中,将常量 0.1f
改为 0
后,性能可能会降低 10 倍。这主要与非规范化浮点数(denormalized floating-point)有关。非规范化数是一种特殊的浮点数表示,用于在浮点数表示中获取非常接近零的额外值。然而,对非规范化浮点数的操作比规范化浮点数慢得多,因为许多处理器不能直接处理它们,必须使用微代码进行捕获和解析。
实现步骤
测试代码示例
1 |
|
代码解释
- 代码中定义了两个常量数组
x
和z
,以及一个数组y
。 - 通过嵌套循环对
y
数组进行多次乘法、除法和加法、减法操作。 - 根据是否定义
FLOATING
宏,分别进行y[i]=y[i]+0.1f; y[i]=y[i]-0.1f;
或y[i]=y[i]+0; y[i]=y[i]-0;
操作。 - 记录程序运行时间并输出结果。
核心代码
测试代码
上述的测试代码展示了不同操作下的性能差异。
消除非规范化数的方法
1 |
|
最佳实践
- 尽量避免产生非规范化浮点数。可以通过在代码中添加
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
和_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
来将非规范化数直接舍入为零,从而提高性能。 - 在编写代码时,要注意浮点运算的精度问题,避免不必要的小数值计算导致非规范化数的产生。
常见问题
为什么 y[i]=y[i]+0.1f; y[i]=y[i]-0.1f;
不是无操作?
因为浮点数的精度有限,当 y[i]
是一个非常小的数时,加上 0.1f
后再减去 0.1f
可能会导致精度丢失,从而避免了非规范化数的产生,使后续的浮点运算保持快速。
不同编译器和环境对非规范化数的处理有什么不同?
不同的编译器和环境对非规范化数的处理方式可能不同。例如,某些 GCC 环境和 Visual Studio 环境对消除非规范化数的方法支持程度不同。因此,在实际应用中,需要根据具体的编译器和环境选择合适的方法。
非规范化数对性能的影响在不同 CPU 上是否相同?
不同的 CPU 对非规范化数的处理能力不同,因此非规范化数对性能的影响也会有所差异。例如,在某些 CPU 上,非规范化数的运算可能只比规范化数慢一点,而在其他 CPU 上可能会慢很多。
为何将 0.1f 改为 0 会使性能降低 10 倍?
https://119291.xyz/posts/why-changing-01f-to-0-slows-down-performance/