【问题标题】:What is the fastest way to convert float to int on x86在x86上将float转换为int的最快方法是什么
【发布时间】:2010-09-09 20:49:25
【问题描述】:

在 x86 CPU 上将浮点数转换为 int 的最快方法是什么?对于以下任意组合,最好在 C 或程序集中(可以在 C 中内联):

  • 32/64/80 位浮点数 -> 32/64 位整数

我正在寻找一些比让编译器更快的技术。

【问题讨论】:

  • 从 Pentium 5 切换到算术正确的芯片...(让我觉得自己老了的人...)
  • 我在地上打滚。该死——太糟糕了,人们为此贬低了你!
  • :) 真的有 Pentium 5 吗?如果有,很抱歉它确实有 SSE3,因此完全没问题。如果使用得当(参见 SSE3 和 FISTTP cmets)。

标签: c optimization x86 floating-point assembly


【解决方案1】:

Lua 代码库具有以下 sn-p 来执行此操作(请查看 www.lua.org 的 src/luaconf.h)。 如果您找到(SO 找到)更快的方法,我相信他们会很高兴。

哦,lua_Number 表示双倍。 :)

/*
@@ lua_number2int is a macro to convert lua_Number to int.
@@ lua_number2integer is a macro to convert lua_Number to lua_Integer.
** CHANGE them if you know a faster way to convert a lua_Number to
** int (with any rounding method and without throwing errors) in your
** system. In Pentium machines, a naive typecast from double to int
** in C is extremely slow, so any alternative is worth trying.
*/

/* On a Pentium, resort to a trick */
#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \
    (defined(__i386) || defined (_M_IX86) || defined(__i386__))

/* On a Microsoft compiler, use assembler */
#if defined(_MSC_VER)

#define lua_number2int(i,d)   __asm fld d   __asm fistp i
#define lua_number2integer(i,n)     lua_number2int(i, n)

/* the next trick should work on any Pentium, but sometimes clashes
   with a DirectX idiosyncrasy */
#else

union luai_Cast { double l_d; long l_l; };
#define lua_number2int(i,d) \
  { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; }
#define lua_number2integer(i,n)     lua_number2int(i, n)

#endif

/* this option always works, but may be slow */
#else
#define lua_number2int(i,d) ((i)=(int)(d))
#define lua_number2integer(i,d) ((i)=(lua_Integer)(d))

#endif

【讨论】:

    【解决方案2】:

    使用 SSE 的打包转换是迄今为止最快的方法,因为您可以在同一指令中转换多个值。 ffmpeg 对此有很多汇编(主要用于将音频的解码输出转换为整数样本);检查它以获取一些示例。

    【讨论】:

    • 这是一个很好的建议,但我会警告说它假设了两件事: - 你有一个带有 SSE (>PII) 或 SSE2 (>PIII) 的 x86 处理器 - 你实际上是这样做的想要截断,而不是舍入,转换
    • 还要注意限制,这当然不是 80 位浮点值的选项
    【解决方案3】:

    我假设需要截断,就像在“C”中写i = (int)f一样。

    如果你有 SSE3,你可以使用:

    int convert(float x)
    {
        int n;
        __asm {
            fld x
            fisttp n // the extra 't' means truncate
        }
        return n;
    }
    

    或者,使用 SSE2(或在 x64 中内联汇编可能不可用),您可以使用几乎一样快:

    #include <xmmintrin.h>
    int convert(float x)
    {
        return _mm_cvtt_ss2si(_mm_load_ss(&x)); // extra 't' means truncate
    }
    

    在较旧的计算机上,可以选择手动设置舍入模式并使用普通的fistp 指令执行转换。这可能仅适用于浮点数组,否则必须注意不要使用任何会使编译器更改舍入模式的构造(例如强制转换)。这样做是这样的:

    void Set_Trunc()
    {
        // cw is a 16-bit register [_ _ _ ic rc1 rc0 pc1 pc0 iem _ pm um om zm dm im]
        __asm {
            push ax // use stack to store the control word
            fnstcw word ptr [esp]
            fwait // needed to make sure the control word is there
            mov ax, word ptr [esp] // or pop ax ...
            or ax, 0xc00 // set both rc bits (alternately "or ah, 0xc")
            mov word ptr [esp], ax // ... and push ax
            fldcw word ptr [esp]
            pop ax
        }
    }
    
    void convertArray(int *dest, const float *src, int n)
    {
        Set_Trunc();
        __asm {
            mov eax, src
            mov edx, dest
            mov ecx, n // load loop variables
    
            cmp ecx, 0
            je bottom // handle zero-length arrays
    
        top:
            fld dword ptr [eax]
            fistp dword ptr [edx]
            loop top // decrement ecx, jump to top
        bottom:
        }
    }
    

    请注意,内联程序集仅适用于 Microsoft 的 Visual Studio 编译器(可能还有 Borland),必须将其重写为 GNU 程序集才能使用 gcc 进行编译。 但是,具有内在函数的 SSE2 解决方案应该非常便携。

    其他舍入模式可以通过不同的 SSE2 内在函数或手动将 FPU 控制字设置为不同的舍入模式。

    【讨论】:

    • 重新内联汇编:是的,Embarcadero(以前的 Borland)确实支持它(C++ 和 Delphi 编译器都支持)
    【解决方案4】:

    由于 MS 将我们从 X64 中的内联汇编中分离出来并迫使我们使用内在函数,因此我查找了使用哪个。 MSDN doc_mm_cvtsd_si64x 一个例子。

    该示例有效,但效率极低,使用 2 个双精度的未对齐加载,我们只需要一个加载,因此摆脱了额外的对齐要求。然后产生了很多不必要的加载和重新加载,但可以通过以下方式消除它们:

     #include <intrin.h>
     #pragma intrinsic(_mm_cvtsd_si64x)
     long long _inline double2int(const double &d)
     {
         return _mm_cvtsd_si64x(*(__m128d*)&d);
     }
    

    结果:

            i=double2int(d);
    000000013F651085  cvtsd2si    rax,mmword ptr [rsp+38h]  
    000000013F65108C  mov         qword ptr [rsp+28h],rax  
    

    可以不使用内联汇编设置舍入模式,例如

        _control87(_RC_NEAR,_MCW_RC);
    

    默认情况下四舍五入(无论如何)。

    我猜,是在每次调用时设置舍入模式还是假设它会被恢复(第三方库)的问题必须通过经验来回答。 您必须为 _control87() 和相关常量添加 float.h

    而且,不,这不适用于 32 位,所以请继续使用 FISTP 指令:

    _asm fld d
    _asm fistp i
    

    【讨论】:

    • 这很有趣,而且似乎是正确的,但在我的测试中,x64 编译器实际上为您的代码生成 完全相同的代码(使用反汇编程序验证),并且MSDN 示例。
    【解决方案5】:

    普通 x86/x87 代码的一个常用技巧是强制浮点数的尾数部分表示 int。接下来是 32 位版本。

    64 位版本是类比的。上面贴的 Lua 版本速度更快,但依赖于 double 到 32 位结果的截断,因此需要将 x87 单元设置为双精度,并且不能适应 double 到 64 位 int 的转换。

    这段代码的好处是它对于所有符合 IEEE 754 的平台都是完全可移植的,唯一的假设是浮点舍入模式设置为最接近。注意:在它编译和工作的意义上是可移植的。 x86 以外的平台通常不会从这种技术中受益多少,如果有的话。

    static const float Snapper=3<<22;
    
    union UFloatInt {
     int i;
     float f;
    };
    
    /** by Vlad Kaipetsky
    portable assuming FP24 set to nearest rounding mode
    efficient on x86 platform
    */
    inline int toInt( float fval )
    {
      Assert( fabs(fval)<=0x003fffff ); // only 23 bit values handled
      UFloatInt &fi = *(UFloatInt *)&fval;
      fi.f += Snapper;
      return ( (fi.i)&0x007fffff ) - 0x00400000;
    }
    

    【讨论】:

    • 对于无符号整数,它可以更简单: inline uint32_t toInt( float fval ) { static float const snapper = 1(uint32_t)fval) & 0x007FFFFF; }
    • static float const snapper; 使这比必要的慢。只需写fval += 1&lt;&lt;23;
    • 在 x86 上它并不慢,因为生成的代码是相同的。在 x87 上没有采用立即参数的 FPU 指令。
    【解决方案6】:

    如果您可以保证运行您的代码的 CPU 与 SSE3 兼容(即使 Pentium 5 也是 JBB),您可以允许编译器使用其 FISTTP 指令(即 -msse3 用于 gcc)。它似乎一直在做应该做的事情:

    http://software.intel.com/en-us/articles/how-to-implement-the-fisttp-streaming-simd-extensions-3-instruction/

    请注意,FISTTP 与 FISTP 不同(后者存在问题,导致运行缓慢)。它是 SSE3 的一部分,但实际上是(唯一的)X87 方面的改进。

    无论如何,其他 X86 CPU 可能会很好地进行转换。 :)

    Processors with SSE3 support

    【讨论】:

      【解决方案7】:

      如果您真的关心这个速度,请确保您的编译器正在生成 FIST 指令。在 MSVC 中,您可以使用 /QIfist、see this MSDN overview

      执行此操作

      您也可以考虑使用 SSE 内在函数为您完成工作,请参阅 Intel 的这篇文章:http://softwarecommunity.intel.com/articles/eng/2076.htm

      【讨论】:

        【解决方案8】:

        通常,您可以相信编译器是高效且正确的。为编译器中已经存在的东西滚动你自己的函数通常不会有任何收获。

        【讨论】:

        • 你完全不正确。在这种情况下,您自己的滚动是一个非常明显的 10 倍内置函数的速度改进,因为当您自己执行此操作时,您可以信任 FPU 标志的状态,而内置 _ftol 不执行此操作,或者您可以使用并行执行上交所。
        • 或者您可以标记 '-msse3' (gcc) 并让 'fixed' FTSTTP 正确、无缝地完成。
        • 编译器提供的例程不太适合性能至关重要的多媒体应用程序
        【解决方案9】:

        这取决于您是要截断转换还是舍入转换以及精度。默认情况下,当您从 float 转换为 int 时,C 将执行截断转换。有 FPU 指令可以做到这一点,但它不是 ANSI C 转换,使用它有很多注意事项(例如了解 FPU 舍入状态)。由于您的问题的答案相当复杂,并且取决于您尚未表达的一些变量,因此我推荐这篇关于该问题的文章:

        http://www.stereopsis.com/FPU.html

        【讨论】:

          【解决方案10】:

          在汇编中有一条指令可以将浮点数转换为 int:使用 FISTP 指令。它从浮点堆栈中弹出值,将其转换为整数,然后存储在指定的地址处。我认为不会有更快的方法(除非您使用我不熟悉的扩展指令集,如 MMX 或 SSE)。

          另一条指令 FIST 将值留在 FP 堆栈上,但我不确定它是否适用于四字大小的目标。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-05-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-29
            • 2013-02-17
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多