25

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

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

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

4

10 回答 10

18

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

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

于 2008-09-17T00:34:40.867 回答
14

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

于 2008-09-17T00:24:10.407 回答
8

普通 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;
}
于 2008-09-23T07:19:57.773 回答
7

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

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

于 2008-09-17T00:27:00.557 回答
7

Lua 代码库有以下代码片段来执行此操作(请查看 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
于 2008-09-17T01:05:58.387 回答
7

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

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

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

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

支持 SSE3 的处理器

于 2009-03-15T11:34:23.703 回答
5

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

如果你有 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 控制字设置为不同的舍入模式来实现。

于 2014-02-26T17:28:59.860 回答
3

如果您真的关心这个速度,请确保您的编译器正在生成 FIST 指令。在 MSVC 中,您可以使用 /QIfist 执行此操作,请参阅此 MSDN 概述

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

于 2008-09-17T00:29:51.783 回答
3

由于 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);

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

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

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

_asm fld d
_asm fistp i
于 2013-06-01T15:32:43.827 回答
-8

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

于 2008-09-17T00:35:57.587 回答