CSS   发布时间:2022-04-17  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了stl – 通过将float放入int变量来进行内联ASM舍入的优点大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
我继承了一段非常有趣的代码:
inlinE int round(float a)
{
  int i;
  __asm {
    fld   a
    fistp i
  }
  return i;
}

我的第一个冲动是丢弃它并用(int)std :: round替换调用(前C 11,如果今天发生的话会使用std :: lround),但过了一段时间我开始怀疑它是否有一些优点毕竟…

这个函数的用例都是[-100,100]中的值,所以即使int8_t也足够宽以保存结果.然而,fistp需要至少32位的内存变量,因此小于int32_t就像浪费更多.

现在,很明显将float转换为int并不是最快的方法,因为舍入模式必须按照标准切换到截断,然后再返回. C 11提供了std :: lround函数,它可以缓解这一特定问题,但虑到值通过float-> long-> int而不是直接到达应该的位置,它似乎仍然更浪费.

另一方面,在函数中使用内联ASM,编译器无法将i优化到寄存器中(即使它可以,Help期望一个内存变量),所以std :: lround看起来并不太糟糕……

然而,我所遇到的最紧迫的问题是假设(如此函数所做的那样)是多么安全,舍入模式将始终是舍入到最接近的,因为它显然是(没有检查).由于std :: lround必须保证独立于舍入模式的某种行为,这个假设,只要它成立,似乎总是使内联ASM轮更好的选择.

另外我很不清楚std :: fesetround设置的舍入模式和std :: lround替代std :: lrint使用的舍入模式以及Help ASM指令中使用的舍入模式是否保证相同或至少同步.

这些是我的虑因素,也就是我不知道如何做出保留或更换功能的明智决定.

现在回答问题:

根据对这些虑因素的更为明智的看法或我没有想到的这些虑因素,使用此功能似乎是否合适?

如果有风险,风险有多大?

推理是否存在为什么它不会比std :: lround或std :: lrint快?

没有性能成本可以进一步改进吗?

如果程序是为x86-64编译的,这种推理是否有任何改变?

解决方法

TL; DR:使用lrintf(X)或(int)nearbyintf(X),具体取决于编译器喜欢哪一个. (检查asm以查看哪一个内联,有或没有-ffast-math)

当你可以避免它时,不要使用内联asm.编译器不“理解”它的作用,因此无法通过它进行优化.例如如果在某个地方内联该函数使其参数成为编译时常量,那么它仍将fld一个常量并将其压入内存,然后将其加载回整数寄存器.纯C将让编译器传播常量并且只传播mov r32,imm32,或者进一步传播常量并将其折叠成其他东西.更不用说CSE,并将转换提升出循环. (MSVC inline asm doesn’t let you specify that an asm block is a pure function,and only needs to be run if the output value is needed,and that it doesn’t depend on a global. GNU C inline asm允许该部分,但它仍然是一个糟糕的选择,因为它对编译器不透明).

The GCC wiki even has a page on this subject,解释与前一段(以及更多)相同的内容,因此内联asm绝对应该是最后的手段.

在这种情况下,我们可以让编译器从纯C中发出好的代码,所以我们绝对应该这样做.

使用当前舍入模式的Float-> int只需要一条机器指令(见下文),但诀窍是让编译器发出它(只有它).将数学库函数设置为内联可能很棘手,因为在某些情况下,它们中的一些必须设置errno和/或引发不精确的异常. (-fno-math-errno可以提供帮助,如果你不能使用完整的-ffast-math或MSVC等价物)

但是,它并不理想:float-> long-> int与它们的大小不同时直接与int不同. x86-64 SystemV ABI(除了Windows之外的所有东西都使用)有64位长.

64位长度改变了lrint的溢出语义:而不是获得0x80000000(在带有SSE指令的x86上),你将获得长的低32位(如果值超出长的范围,则将为全零).

此lrintf不会自动向量化(除非编译器可以证明浮点数将在范围内),因为只有标量而非SIMD指令将浮点数或双精度转换为压缩的64位整数(until AVX512DQ). C数学库函数的IDK直接转换为int,但你可以使用(int)nearbyintf(X),它可以在64位代码中更容易地自动矢量化.请参阅下面的部分,了解gcc和clang如何做到这一点.

然而,除了击败自动矢量化之外,对于任何现代微体系结构的cvtss2si rax,xmm0都没有直接的速度惩罚(见Agner Fog’s insn tables).它只需花费REX前缀的额外指令字节.

在AArch64(又名ARM64),gcc4.8 compiles lround into a single fcvtas x0,s0 instruction,所以我猜ARM64在硬件中提供了时髦的舍入模式(但x86没有).奇怪的是,-ffast-math使内联函数更少,但这与笨重的旧gcc4.8有关.对于ARM(不是64),即使使用-mfloat-abi = hard -mhard-float -march = armv7-a,gcc4.8也不会内联任何内容.也许那些不是正确的选择; IDK ARM非常好:/

如果您要进行大量转换,可以使用SSE / AVX内在函数like _mm_cvtps_epi32(cvtps2dq)手动为x86进行矢量化,甚至将生成的32位整数元素打包为16位或8位(使用packssdw.但是,使用纯C编译器可以自动矢量化是一个很好的计划,因为它是可移植的.

lrintf

#include <math.h>
int round_to_nearest(float f) {  // default mode is always nearest
  return lrintf(f);
}

编译器输出the Godbolt Compiler explorer

########### Without -ffast-math #############
    cvtss2si        eax,xmm0    # gcc 6.1  (-O3 -mx32,so long is 32bit)

    cvtss2si        rax,xmm0    # gcc 4.4 through 6.1  (-O3).  can't auto-vectorize,though.

    jmp     lrintf               # clang 3.8 (-O3 -msse4.1),still tail-calls the function :/

             ###### With -ffast-math #########
    jmp     lrintf               # clang 3.8 (-O3 -msse4.1 -ffast-math)

所以显然clang并不能很好地应对它,但即使是古老的gcc也很棒,即使没有-ffast-math也能做得很好.

Don’t use roundf/lroundf:它具有非标准的舍入语义(距离0的中间情况,而不是偶数). This leads to worse x86 asm,但实际上更好的ARM64 asm.那么也许可以将它用于ARM?但它确实具有固定的舍入行为,而不是使用当前的舍入模式.

如果你想将返回值作为一个浮点数而不是转换为int,那么use nearbyintf可能会更好.当输出!=输入时,rint必须提高FP不精确异常. (但SSE4.1 roundss可以实现其立即控制字节的第3位的行为).

直接将nearbyint()截断为int.

#include <math.h>
int round_to_nearest(float f) {
  return nearbyintf(f);
}

编译器输出the Godbolt Compiler explorer.

########  With -ffast-math ############
    cvtss2si        eax,xmm0      # gcc 4.8 through 6.1 (-O3 -ffast-math)

    # clang is dumb and won't fold the roundss into the cvt.  Without sse4.1,it's a function call
    roundss xmm0,xmm0,12         # clang 3.5 to 3.8 (-O3 -ffast-math -msse4.1)
    cvttss2si       eax,xmm0

    roundss   xmm1,12      # ICC13 (-O3 -msse4.1 -ffast-math)
    cvtss2si  eax,xmm1

        ######## WITHOUT -ffast-math ############
    sub     rsp,8
    call    nearbyintf                    # gcc 6.1 (-O3 -msse4.1)
    add     rsp,8                        # and clang without -msse4.1
    cvttss2si       eax,xmm0

    roundss xmm0,12               # clang3.2 and later (-O3 -msse4.1)
    cvttss2si       eax,12             # ICC13 (-O3 -msse4.1)
    cvtss2si  eax,xmm1

Gcc 4.7及更早版本:只是没有-msse4.1的cvttss2si,但如果SSE4.1可用,则会发出一个回合.它的附近定义必须使用inline-asm,因为asm语法在intel-syntax输出中被破坏.可能这是它插入的方式,然后当它意识到它转换为int时没有被优化掉.

它在asm中是如何工作的

如果您的目标是没有SSE的20年前的CPU,那就是这样. (你说浮动,而不是双倍,所以我们只需要SSE,而不是SSE2.没有SSE2的最老的CPU是Athlon Xp).

现代系统在xmm寄存器中执行浮点运算. SSE有转换scalar float to signed int with truncation (cvttss2si)with the current counting mode (cvtss2si)的指令.(注意第一个中Truncate的额外t.其余的助记符是Convert Scalar Single-precision to Signed Integer.)有类似的double指令,x86-64允许目标是64位整数寄存器.

另请参阅x86标记wiki.

cvtss2si基本上存在是因为C的浮点数为int的默认行为.更改舍入模式的速度很慢,因此英特尔提供了一种不吸引人的方法.

我认为即使32位版本的现代Windows也需要足够新的硬件才能拥有SSE2,以防万一对任何人都很重要. (SSE2是AMD64 ISA的一部分,64位调用约定甚至在xmm寄存器中传递float / double args).

大佬总结

以上是大佬教程为你收集整理的stl – 通过将float放入int变量来进行内联ASM舍入的优点全部内容,希望文章能够帮你解决stl – 通过将float放入int变量来进行内联ASM舍入的优点所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。