次正规浮点数在Intel处理器上开销巨大…
摘要
本文基准测试了Intel、AMD和ARM处理器上次正规浮点数的性能,显示Intel处理器在使用次正规数时出现显著减速,而AMD和ARM则不受影响。
<p><a href="https://lobste.rs/s/hv3l4v/subnormal_floating_point_numbers_are">评论</a></p>
查看缓存全文
缓存时间: 2026/09/15 21:18
# 次正规浮点数在英特尔处理器上代价高昂...
来源:https://lemire.me/blog/2026/09/15/subnormal-floating-point-numbers-are-expensive-on-intel-processors/
我们使用 IEEE 标准表示浮点数。对于非常小的数值,标准采用特殊的*次正规*数。遗憾的是,它们因拖慢运算速度而声名狼藉。因此,视频游戏程序员和机器学习专家有时会为避免性能损耗而绕过次正规数计算。
它们到底有多慢?让我来测量一下。我编写了一个小型 C++ 基准测试,包含几个内核,操作对象是 16384 个元素的数组(小到足以放入缓存):
- 将每个值乘以 0.75,
- 两个数组相加,
- 将每个值除以 3,
- 将正规值乘以一个极小常量(2⁻¹⁰³⁰),使*输入*为正规数但*输出*为次正规数,
- 一个依赖链 `x *= 0.9999` 重复 16384 次。
对于每个内核,我输入的值要么是正规数(在 `[0.5, 1]` 范围内),要么是次正规数,要么是其中百分之一为次正规数的正规数。编译器被允许对数组计算进行自动向量化。我在 Linux 上使用 GCC 15 配合 `-O3 -march=native` 标志,在 macOS 上使用 Apple clang 17 配合相同标志。我还用 Linux 上的 clang 21 进行了验证。
我在五种处理器上运行了基准测试:
- 英特尔 Xeon 6975P-C(Granite Rapids),在 AWS `c8i.xlarge` 实例上,
- 英特尔 Xeon Gold 6548N(Emerald Rapids),我实验室的一台服务器,
- AMD EPYC 9R45(Zen 5),在 AWS `c8a.xlarge` 实例上,
- AWS Graviton 5(Arm Neoverse V3),在 `c9g.xlarge` 实例上,
- Apple M4 Max。
以下是 `double` 类型数值的结果,单位为纳秒/元素(或纳秒/步)。
*英特尔 Granite Rapids*
| 内核 | 正规数 | 1% 次正规数 | 次正规数 |
|-----------------------|--------|-------------|----------|
| 乘以 0.75 | 0.17 | 0.44 | 8.35 |
| 两个数组相加 | 0.20 | 0.18 | 0.18 |
| 除以 3 | 0.51 | 0.85 | 9.38 |
| 正规数输入,次正规数输出 | 0.17 | 8.58 | — |
| 依赖链 | 0.77 | 32.69 | — |
*英特尔 Emerald Rapids*
| 内核 | 正规数 | 1% 次正规数 | 次正规数 |
|-----------------------|--------|-------------|----------|
| 乘以 0.75 | 0.21 | 0.49 | 9.25 |
| 两个数组相加 | 0.23 | 0.25 | 0.25 |
| 除以 3 | 0.57 | 0.94 | 10.40 |
| 正规数输入,次正规数输出 | 0.21 | 9.27 | — |
| 依赖链 | 1.14 | 36.51 | — |
*AMD Zen 5*
| 内核 | 正规数 | 1% 次正规数 | 次正规数 |
|-----------------------|--------|-------------|----------|
| 乘以 0.75 | 0.07 | 0.10 | 0.08 |
| 两个数组相加 | 0.09 | 0.09 | 0.09 |
| 除以 3 | 0.11 | 0.24 | 0.25 |
| 正规数输入,次正规数输出 | 0.07 | 0.07 | — |
| 依赖链 | 0.66 | 0.88 | — |
*AWS Graviton 5*
| 内核 | 正规数 | 1% 次正规数 | 次正规数 |
|-----------------------|--------|-------------|----------|
| 乘以 0.75 | 0.17 | 0.17 | 0.16 |
| 两个数组相加 | 0.19 | 0.20 | 0.20 |
| 除以 3 | 0.30 | 0.30 | 0.30 |
| 正规数输入,次正规数输出 | 0.17 | 0.17 | — |
| 依赖链 | 0.91 | 0.91 | — |
*Apple M4 Max*
| 内核 | 正规数 | 1% 次正规数 | 次正规数 |
|-----------------------|--------|-------------|----------|
| 乘以 0.75 | 0.06 | 0.06 | 0.06 |
| 两个数组相加 | 0.12 | 0.12 | 0.12 |
| 除以 3 | 0.11 | 0.11 | 0.11 |
| 正规数输入,次正规数输出 | 0.06 | 0.06 | — |
| 依赖链 | 0.72 | 0.75 | — |
在英特尔处理器上,涉及次正规数的乘法运算比正规数运算慢约 45 到 50 倍。除法运算慢约 18 倍。依赖链中,每次乘法都需等待前一次完成,其耗时从约 1 纳秒/步飙升至超过 30 纳秒/步。依赖链中的正规数乘法延迟为 4 个周期,而次正规数则长达 128 个周期。无论次正规数是输入还是输出,结果同样缓慢:将正规数相乘得到次正规数结果,其速度与直接操作次正规数一样慢。例外是加法和减法:它们仍能全速运行。即使次正规数很少(仅占 1%),在英特尔平台上的开销依然显著,因为当编译器对计算进行向量化时,单个次正规数就可能拖慢整个计算块。
AMD 表现好得多。在 Zen 5 上,无论输入如何,乘法和加法都以全速运行。依赖乘法链仅慢了约三分之一(0.66 纳秒到 0.88 纳秒/步):乘法器大约需要额外一个周期来处理次正规数。除法运算大约慢两倍。有趣的是,在除法运算中,百分之一的次正规数与全部是次正规数一样慢。两款 Arm 处理器——Graviton 5 和 Apple M4 Max——则完全不受影响。次正规数可以全速处理。
因此,最新 AMD 和 ARM 处理器上次正规数可能不再是问题。但在英特尔处理器上,它们仍然是一个严重的性能瓶颈。
[我的源代码已发布 (https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2026/09/15)。
## 发布者
## 文章导航
相似文章
中间浮点精度
本文探讨了C++代码中的中间浮点精度如何依赖于编译器设置、CPU标志和架构,尤其是在x87 FPU上,以及这如何影响性能和计算结果。
CPU和GPU上的Floor与Ceil函数对非规格化数的行为
探讨当应用于非规格化浮点数时,floor和ceil函数的行为,强调CPU和GPU实现之间的差异及潜在陷阱。
当浮点数除法胜过整数除法
一篇博客文章,解释了一个反直觉的优化现象:在现代CPU上,使用浮点数除法(DIVSD)比整数除法(IDIVQ)性能更佳,并附有基准测试和汇编分析。
Intel 8087 浮点芯片微码中的条件
对 Intel 8087 浮点协处理器微码中使用的条件测试的详细研究,是逆向工程工作的一部分,旨在理解其算法。
将整数除法转换为浮点除法是微不足道的
一篇技术博客文章,解释了如何使用浮点除法和融合乘加来执行整数除法和取余,并给出了操作数位宽的限制,同时讨论了SIMD和舍入模式的实际注意事项。