对于已经进行过测量或对此类注意事项有深入了解的人,假定您必须执行以下操作(为示例选择任何一个):浮点运算符:
float calc(float y, float z)
{ return sqrt(y * y + z * z) / 100; }
[y
和z
可以是非正规数,让我们假设两种可能的情况,其中y,z或完全随机地都可以是非正规数
现在假设我想避免处理非正规数的性能损失,我只想将它们视为0,然后通过以下方式更改该代码段:
float calc(float y, float z)
{
bool yzero = y < 1e-37;
bool zzero = z < 1e-37;
bool all_zero = yzero and zzero;
bool some_zero = yzero != zzero;
if (all_zero)
return 0f;
float ret;
if (!some_zero) ret = sqrt(y * y + z * z);
else if (yzero) ret = z;
else if (zzero) ret = y;
return ret / 100;
}
更糟糕的是,分支预测错误的性能损失(对于50%或<1%的情况),或者使用非正常数字的性能损失?
为了正确地解释上一段代码中哪些操作可以正常还是不正常,我还希望针对以下紧密相关的问题获得一些单行但完全可选的答案:
float x = 0f; // Will x be just 0 or maybe some number like 1e-40;
float y = 0.; // I assume the conversion is just thin-air here and the compiler will see just a 0.
0; // Is "exact zero" a normal or a denormal number?
float z = x / 1; // Will this "no-op" (x == 0) cause z be something like 1e-40 and thus denormal?
float zz = x / c; // What about a "no-op" operating against any compiler-time constant?
bool yzero = y < 1e-37; // Have comparisions any performance penalty when y is denormal or they don't?
-ffast-math
或等效版本进行编译时,大多数编译器会在启动期间设置这些标志。如果结果差异如此之大,x86微体系结构将是我的主要用例是的,它们可能相差很大。
IIRC,AMD CPU对不正常的输入/结果不收取任何罚款。像GPU一样,它们采用的方法更倾向于吞吐量而不是延迟,并且FPU具有足够的流水线级来处理相当于无分支的硬件中的次规范。即使这可能意味着每个FP操作的等待时间都较长。
从历史上看,Intel过去总是总是非常缓慢地使用微码来辅助低于标准的结果(以及低于标准的输入?)。现代的Intel CPU(Sandybridge系列)不需要微码辅助就可以对非正规操作数执行部分但不是全部FP操作。
在Sandybridge系列上,微码辅助就像一个例外,会刷新乱序的管道,并需要100多个周期,而分支未命中则需要10到20个周期。还有现代CPU上的branch misses have "fast recovery"。真正的分支未命中损失取决于周围的代码。例如如果分支条件确实准备得太晚,则可能会导致放弃许多以后的独立工作。但是,如果您希望微码辅助频繁发生,它可能仍然会更糟。
Agner Fog's microarch PDF有一些信息;他一般都提到这一点,而没有每个uarch的详细细分。我不认为https://uops.info/会测试正常人与不正常人的关系。
现在假定我想避免处理非正规数的性能损失,我只想将它们视为0某些/大多数(?)现代FPU(包括x86 SSE但不包括旧版x87)使您可以免费将零以下子范数(也称为反范数)视为零,因此仅当您希望对[[some函数使用此行为时,才会出现此问题,但是并非全部,都在同一线程中。而且由于切换粒度太细,因此不值得将FP控制寄存器更改为FTZ并返回。然后您应该设置您的FPU免费为您执行此操作,从而消除了来自非正常状态的所有可能的处罚。
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,以了解其跨平台可用性。>>
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,在大多数OS上,线程从主线程继承MXCSR设置。DAZ =反常态为零,将输入次正态视为零。这会影响比较(无论他们是否经历了放慢速度),使得除了在位模式上使用整数填充之外,甚至无法分辨0
与次标准之间的差异。
特定随机问题:
float x = 0f; // Will x be just 0 or maybe some number like 1e-40;
0x00000000
)为IEEE binary32浮点数,因此,这绝对是在使用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
以外的任何内容。
再说一次,普通法线不会突然出现。到最接近的可表示FP值,一直到尾数的最后一位。仅当精确结果不能表示为浮点数或双精度数时,才发生舍入的“错误”。>> IEEE“基本”操作(* / +-和
sqrt
)的最大允许错误为0.5 ulp,即精确结果必须正确舍入
bool yzero = y < 1e-37; // Have comparisons any performance penalty when y is denormal or they don't?
是的,他们可以。这是将输入移位以使其二进制位值对齐的一种特殊情况,尾数的隐含前导数字为0
而不是1
。因此,硬件可能会选择不在快速路径上处理该问题,而是采用微码协助。[在Intel Skylake上,将两个次法线与vcmplt_oqpd
进行比较不会导致任何减慢。截断或最接近的整数都不转换。 (cvt[t]ps2dq
或等价的pd。)例如,根据我的测试,例如this recent proposed LLVM change is safe on Skylake。