在包括 x86 在内的许多 ISA 中都有免费的硬件支持,请参见下面的回复:FTZ / DAZ。当您使用 -ffast-math 或等效项进行编译时,大多数编译器会在启动期间设置这些标志。
另请注意,在某些情况下,您的代码无法避免惩罚(在有任何惩罚的硬件上):y * y 或 z * z 对于小但规范化的 y 或 z 可能低于正常值/强>。 (Good catch, @chtz)。 y*y 的指数是y 指数的两倍,更负或更正。对于23 explicit mantissa bits in a float,这大约是 12 个指数值,它们是次正规值的平方根,不会一直下溢到 0。
平方次正规总是给0 下溢;我不知道,对于乘法,次正规输入可能比次正规输出受到惩罚的可能性更小。 是否有低于正常值的惩罚会因一个微架构中的操作而异,例如加/减、乘和除。
此外,任何负数 y 或 z 都会被视为 0,这可能是一个错误,除非您的输入已知为非负数。
如果结果差异如此之大,x86 微架构将是我的主要用例
是的,处罚(或没有处罚)差别很大。
从历史上看(P6 系列),英特尔过去总是采用非常慢的微码辅助来处理不正常的结果和不正常的输入,包括比较。现代英特尔 CPU(Sandybridge 系列)无需微码辅助即可处理次正规操作数上的部分但不是全部 FP 操作。 (性能事件fp_assists.any)
微码辅助就像一个异常,会刷新乱序的管道,在 SnB 系列上需要超过 160 个周期,而分支未命中则需要大约 10 到 20 个周期。
以及现代 CPU 上的 branch misses have "fast recovery"。真正的分支未命中惩罚取决于周围的代码;例如如果分支条件真的很晚才准备好,它可能会导致丢弃大量后来的独立工作。但是,如果您期望它经常发生,那么微码辅助可能仍然会更糟。
请注意,您可以使用整数运算检查次正规:只需检查指数字段是否为全零(尾数为非零:0.0 的全零编码在技术上是次正规的特殊情况) . 因此您可以使用andps/pcmpeqd/andps 等整数 SIMD 操作手动刷新为零@
Agner Fog's microarch PDF 有一些信息;他笼统地提到了这一点,但没有对每个 uarch 进行完全详细的细分。不幸的是,我不认为 https://uops.info/ 测试正常与不正常。
Knight's Landing (KNL) 对除法只有低于正常值的惩罚,而不是 add / mul。与 GPU 一样,他们采用了一种优先考虑吞吐量而不是延迟的方法,并且在其 FPU 中有足够的流水线阶段来处理硬件中的次规范,相当于无分支。尽管这可能意味着每个 FP 操作的延迟更高。
除非设置了 FTZ,否则 AMD Bulldozer / Piledriver 对“低于正常或下溢”的结果有约 175 个周期的惩罚。 Agner 没有提到次正常的输入。压路机/挖掘机没有任何处罚。
AMD Ryzen(来自 Agner Fog 的 microarch pdf)
产生次等结果的浮点运算需要额外的几个时钟周期。这
当乘法或除法下溢为零时也是如此。这远远小于
推土机和打桩机的高额罚款。清零时没有惩罚
mode 和 denormals-are-zero 模式都打开了。
相比之下,英特尔 Sandybridge 系列(至少 Skylake)不会对一直下溢至 0.0 的结果进行处罚。
Intel Silvermont (Atom)来自 Agner Fog 的 microarch pdf
具有次正规数作为输入或输出或产生下溢的操作采取
大约 160 个时钟周期,除非清零模式和非规范化为零
模式都使用了。
这将包括比较。
我不知道任何非 x86 微架构的详细信息,例如 ARM cortex-a76 或任何 RISC-V,因此我无法挑选一些可能也相关的随机示例。在简单的有序管道与现代 x86 等深度 OoO exec CPU 之间,错误预测的惩罚也有很大差异。真正的误判惩罚也取决于周围的代码。
现在假设我想避免处理非正规数的性能损失,我只想将它们视为 0
那么你应该设置你的 FPU 来免费为你做这件事,消除所有不正常的惩罚的可能性。
一些/大多数(?)现代 FPU(包括 x86 SSE 但不包括旧版 x87)允许您免费将次正规(又名非正规)视为零,因此只有当您希望 some 有这种行为时才会出现此问题em> 函数,但不是全部,在同一个线程中。并且切换太细粒度不值得将 FP 控制寄存器更改为 FTZ 并返回。
或者,如果你想编写完全可移植的代码,但它在任何地方都很糟糕,即使这意味着忽略硬件支持并因此比它可能的速度要慢。
Some x86 CPUs do even rename MXCSR 因此更改舍入模式或 FTZ/DAZ 可能不必耗尽无序后端。它仍然不便宜,您应该避免每隔几条 FP 指令就执行一次。
ARM 还支持类似的功能:subnormal IEEE 754 floating point numbers support on iOS ARM devices (iPhone 4) - 但显然 ARM VFP / NEON 的默认设置是将次正规视为零,有利于性能而不是严格的 IEEE 合规性。
另请参阅flush-to-zero behavior in floating-point arithmetic,了解此功能的跨平台可用性。
在 x86 上,具体机制是您在 MXCSR 寄存器中设置 DAZ 和 FTZ 位(SSE FP 数学控制寄存器;还有用于 FP 舍入模式、FP 异常掩码和粘性 FP 的位屏蔽异常状态位)。 https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz 展示了布局,还讨论了对旧版 Intel CPU 的一些性能影响。很多好的背景/介绍。
使用-ffast-math 编译会在调用main 之前链接一些额外的启动代码来设置FTZ/DAZ。 IIRC,线程在大多数操作系统上从主线程继承MXCSR 设置。 p>
- DAZ = 非正规为零,将输入次正规视为零。这会影响比较(无论他们是否会经历减速),因此除了在位模式上使用整数内容之外,甚至无法区分
0 和次规范之间的区别。
- FTZ = 清零,计算的次正常输出只是下溢归零。即禁用逐渐下溢。 (请注意,将两个小的正态数相乘可能会下溢。我认为除低位之外的尾数抵消的正态数的加法/减法也可能产生次正态数。)
通常您只需设置两者或都不设置。如果您正在处理来自另一个线程或进程的输入数据,或者是编译时常量,即使您生成的所有结果都已归一化或为 0,您仍然可能有次等输入。
具体随机问题:
float x = 0f; // Will x be just 0 or maybe some number like 1e-40;
这是一个语法错误。大概你的意思是0.f或0.0f
0.0f 完全可以表示为 IEEE binary32 浮点数(使用位模式0x00000000),因此这绝对是您在任何使用 IEEE FP 的平台上都会得到的。你不会随机得到不是你写的次正规的。
float z = x / 1; // Will this "no-op" (x == 0) cause z be something like 1e-40 and thus denormal?
不,IEEE754 不允许 0.0 / 1.0 提供除 0.0 以外的任何内容。
再一次,次常态不会凭空出现。 只有在不能将精确结果表示为浮点数或双精度数时才会发生舍入“错误”。 IEEE“基本”操作(* / + - 和 sqrt)的最大允许错误是0.5 ulp,即准确的结果必须正确舍入到最接近的可表示的 FP 值,一直到尾数的最后一位。
bool yzero = y < 1e-37; // Have comparisons any performance penalty when y is denormal or they don't?
也许,也许不是。对最近的 AMD 或 Intel 没有惩罚,但在 Core 2 上速度较慢。
请注意,1e-37 的类型为 double,并将导致将 y 提升为 double。您可能希望与使用 1e-37f 相比,这实际上可以避免低于正常水平的处罚。低于标准的 float->int 对 Core 2 没有惩罚,但不幸的是,cvtss2sd 在 Core 2 上仍然有很大的惩罚。(GCC/clang don't optimize away 即使使用 -ffast-math 也可以转换,尽管我认为它们可以,因为 1e-37 正是可以表示为一个平面,并且每个次正规的浮点数都可以精确地表示为一个规范化的双精度。因此,双精度的提升始终是精确的,并且不能改变结果)。
在 Intel Skylake 上,将两个次正规数与 vcmplt_oqpd 进行比较不会导致任何减速,而将 ucomisd 与整数 FLAGS 进行比较也不会。但在 Core 2 上,两者都很慢。
比较,如果像减法一样完成,确实必须移动输入以对齐它们的二进制位值,并且尾数的隐含前导数字是0而不是1,因此次正规是一种特殊情况。因此,硬件可能会选择不在快速路径上处理该问题,而是采用微码辅助。较旧的 x86 硬件可能会处理得更慢。
如果您构建一个与普通添加/子单元分开的特殊比较 ALU,则可能会有所不同。浮点位模式可以作为符号/幅度整数进行比较(NaN 的特殊情况),因为选择了 IEEE 指数偏差来实现这一点。 (即nextafter 只是整数 ++ 或 -- 在位模式上)。但这显然不是硬件的作用。
即使在 Core 2 上,FP 转换为整数也很快。 cvt[t]ps2dq 或 pd 等价物将压缩浮点/双精度转换为 int32,并使用截断或当前舍入模式。所以例如this recent proposed LLVM optimization is safe on Skylake and Core 2,根据我的测试。
同样在 Skylake 上,平方次正规(产生 0)没有惩罚。但它确实对Conroe(P6-family)造成了巨大的惩罚。
但是,即使在 Skylake 上乘以正常数来产生次正常的结果也会有损失(慢约 150 倍)。