gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 15:13:20 26 4
gpt4 key购买 nike

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

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?

    最佳答案

    许多ISA(包括x86)都免费提供硬件支持,请参阅以下re:FTZ / DAZ。当您使用-ffast-math或等效版本进行编译时,大多数编译器会在启动期间设置这些标志。

    还要注意,在某些情况下,您的代码无法避免惩罚(在硬件上存在惩罚):对于较小但已标准化的y * yz * z yz可以是次普通的。 (Good catch, @chtz)。 y*y的指数是y的指数的两倍,负数或正数。使用23 explicit mantissa bits in a float 时,大约是12个指数值,它们是次正规值的平方根,并且不会一直溢出到0

    平方次方总是会使0下溢;我不知道,次乘输入比乘乘的次乘输出更不可能受到惩罚。 是否在一个微体系结构中进行操作,是否具有低于正常的惩罚可能会有所不同,例如加/减vs.乘vs.除法。

    另外,任何负yz都将被视为0,除非您的输入已知为非负数,否则这可能是一个错误。

    if results can vary so widely, x86 microarchitectures will be my main use case



    是的,处罚(或没有处罚)差异很大。

    历史上(P6家族),英特尔过去总是非常缓慢地使用微码辅助功能来处理不正常的结果和不正常的输入,包括进行比较。现代的Intel CPU(Sandybridge系列)不需要微码辅助就可以对次普通操作数执行部分但不是全部FP操作。 (性能事件fp_assists.any)

    微代码辅助就像一个异常(exception),会刷新顺序困惑的流水线,并在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/可以测试正常人和非正常人。

    骑士降落(KNL)仅对分区有不正常的处罚,不能加/多。像GPU一样,他们采用的方法更倾向于吞吐量而不是延迟,并且FPU中有足够的流水线级来处理相当于无分支的硬件中的次规范。即使这可能意味着每个FP操作的等待时间都更长。

    除非设置了FTZ,否则AMD“推土机/打桩机”对“低于正常水平或下溢”的结果有〜175个周期的罚款。阿格纳(Agner)没有提及非正规输入。压路机/挖掘机没有任何处罚。

    AMD Ryzen (from Agner Fog's microarch pdf)

    Floating point operations that give a subnormal result take a few clock cycles extra. The same is the case when a multiplication or division underflows to zero. This is far less than the high penalty on the Bulldozer and Piledriver. There is no penalty when flush-to-zero mode and denormals-are-zero mode are both on.



    相比之下,英特尔Sandybridge家族(至少是Skylake)对结果一直下溢至0.0的结果没有惩罚。

    Intel Silvermont (Atom) from Agner Fog's microarch pdf

    Operations that have subnormal numbers as input or output or generate underflow take approximately 160 clock cycles unless the flush-to-zero mode and denormals-are-zero mode are both used.



    这将包括比较。

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

    And now assume I want to avoid the performance penalty of dealing with denormal numbers and I just want to treat them as 0



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

    某些/大多数(?)现代FPU(包括x86 SSE但不包括旧版x87)可让您免费将零态(也称为反态)视为零,因此,仅当您希望对某些功能执行此操作而不是对同一功能执行全部操作时,才会出现此问题线。而且,由于切换的粒度太细,因此不值得将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寄存器(SSE FP数学控制寄存器;还具有FP舍入模式,FP异常掩码和粘性FP屏蔽异常(exception)状态位)中的DAZ和FTZ位置位。 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设置。
  • DAZ =异常为零,将输入的次级正常视为零。这会影响比较(无论它们是否会变慢),因此除了在位模式上使用整数填充之外,甚至无法分辨0和次规范之间的区别。
  • FTZ =刷新为零,计算中的次标准输出只是下溢为零。即禁用渐进式下溢。 (请注意,将两个较小的普通数相乘可能会下溢。我认为除尾数少之外的尾数都抵消了的普通数的加/减也可能会产生次普通数。)

  • 通常,您只需设置两者或都不设置。如果您正在处理来自另一个线程或进程的输入数据或编译时常量,则即使您产生的所有结果均已归一化或为0,您仍可能具有非正规输入。

    具体随机问题:
    float x = 0f; // Will x be just 0 or maybe some number like 1e-40;

    这是语法错误。大概是0.f0.0f
    0.0f可以作为IEEE binary32 float完全表示(使用位模式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之外的任何内容。

    再次,次正常状态不会显得稀疏。 仅当精确结果不能表示为float或double时才发生舍入“错误”。 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可以精确地表示为一个单位,并且每个次正规float可以精确地表示为规范化的double。因此对double的提升始终是精确的,并且不能更改结果)。

    在Intel Skylake上,将两个次正规量与vcmplt_oqpd进行比较不会导致任何减慢,也不会与ucomisd进行整数FLAGS的比较。但是在Core 2上,两者都很慢。

    比较,如果像减法一样进行,则必须将输入移位以使其二进制位值对齐,并且尾数的隐含前导数字是0而不是1,因此子范式是一种特殊情况。因此,硬件可能会选择不在快速路径上处理该问题,而是采用微码辅助。较旧的x86硬件可能处理得较慢。

    如果您构建了一个与常规添加/子单元分开的特殊比较ALU,则可以采取不同的方法。浮点位模式可以比较为正负号/幅度整数(NaN有特殊情况),因为选择了IEEE指数偏差来实现该功能。 (即nextafter在位模式上只是整数++或-)。但这显然不是硬件的作用。

    即使在Core 2上,FP到整数的转换速度也很快。 cvt[t]ps2dq或等效的pd将打包的float / double转换为带有截断或当前舍入模式的int32。因此,根据我的测试,例如this recent proposed LLVM optimization is safe on Skylake and Core 2

    同样在Skylake上,对次法线进行平方(产生0)不会受到任何惩罚。但这确实对Conroe(P6系列)造成了巨大的损失。

    但是,即使在Skylake上(将慢150倍),将正常数字相乘以产生次标准结果也会带来代价。

    关于c++ - 性能损失:数字归一化与分支错误预测,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60969892/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com