性能损失:归一化数字与分支错误预测

问题描述 投票:1回答:1

对于已经进行过测量或对此类注意事项有深入了解的人,假定您必须执行以下操作(为示例选择任何一个):浮点运算符:

float calc(float y, float z)
{ return sqrt(y * y + z * z) / 100; }

[yz可以是非正规数,让我们假设两种可能的情况,其中y,z或完全随机地都可以是非正规数

  • 50%的时间
  • <1%的时间

现在假设我想避免处理非正规数的性能损失,我只想将它们视为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?
c++ x86 floating-point micro-optimization branch-prediction
1个回答
2
投票
[包括x86在内的许多ISA都免费提供硬件支持,请参阅以下有关re:FTZ / DAZ。当您使用-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/会测试正常人与不正常人的关系。


我不知道任何非x86微体系结构(例如ARM cortex-a76或任何RISC-V)的详细信息,以挑选一些可能​​也相关的随机示例。错误预测的惩罚在简单的有序流水线与深层OoO exec CPU(例如现代x86)之间也存在巨大差异。真正的错误预测惩罚还取决于周围的代码。


现在假定我想避免处理非正规数的性能损失,我只想将它们视为0

然后您应该设置您的FPU免费为您执行此操作,从而消除了来自非正常状态的所有可能的处罚。

某些/大多数(?)现代FPU(包括x86 SSE但不包括旧版x87)使您可以免费将零以下子范数(也称为反范数)视为零,因此仅当您希望对[[some函数使用此行为时,才会出现此问题,但是并非全部,都在同一线程中。而且由于切换粒度太细,因此不值得将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位置1(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,在大多数OS上,线程从主线程继承MXCSR设置。

DAZ =反常态为零,将输入次正态视为零。这会影响比较(无论他们是否经历了放慢速度),使得除了在位模式上使用整数填充之外,甚至无法分辨0与次标准之间的差异。

    FTZ =刷新为零,计算中的次正规输出只是下溢为零。即禁用渐进式下溢。 (请注意,将两个较小的普通数相乘会产生下溢。我认为除尾数少之外尾数都抵消了的普通数的加/减也可能会产生次普通数。)
  • 通常,您只需设置两者或都不设置。如果您正在处理来自另一个线程或进程的输入数据或编译时常量,那么即使您产生的所有结果都已归一化或为0,您仍然可能会有非正规输入。
  • 特定随机问题:


float x = 0f; // Will x be just 0 or maybe some number like 1e-40;

0f可精确表示为(用位模式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以外的任何内容。

再说一次,普通法线不会突然出现。 

仅当精确结果不能表示为浮点数或双精度数时,才发生舍入的“错误”。>> IEEE“基本”操作(* / +-和sqrt)的最大允许错误为0.5 ulp,即精确结果必须

正确舍入

到最接近的可表示FP值,一直到尾数的最后一位。 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
© www.soinside.com 2019 - 2024. All rights reserved.