在 x86 上将 float 转换为 int 的最快方法是什么

What is the fastest way to convert float to int on x86

提问人:robottobor 提问时间:9/17/2008 最后编辑:kristianprobottobor 更新时间:12/15/2014 访问量:30161

问:

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

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

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

C 优化 x86 浮点 程序集

评论

2赞 JBB 9/17/2008
从奔腾 5 切换到可以正确执行数学运算的芯片......(让我觉得自己老了的男人......
0赞 Kevin 9/18/2008
我在地上打滚。的——太可惜了,人们把你贬低了!
0赞 akauppi 3/15/2009
:)真的有奔腾 5 吗?如果有,很抱歉它确实有 SSE3,因此完全没问题。明智地使用时(参见 SSE3 和 FISTTP 评论)。

答:

14赞 Dark Shikari 9/17/2008 #1

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

评论

0赞 Zach Burlingame 9/17/2008
这是一个很好的建议,但我要警告说,它假设了两件事: - 您有一个带有 SSE (>PII) 或 SSE2 (>PIII) 的 x86 处理器 - 事实上,您确实想要截断,而不是舍入转换
0赞 PhiS 7/21/2013
另请注意,这当然不是 80 位浮点值的选项
7赞 dreamlax 9/17/2008 #2

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

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

3赞 Don Neufeld 9/17/2008 #3

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

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

18赞 Zach Burlingame 9/17/2008 #4

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

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

-10赞 user14504 9/17/2008 #5

通常,您可以相信编译器是高效且正确的。为编译器中已存在的内容滚动自己的函数通常不会获得任何好处。

评论

4赞 Don Neufeld 9/17/2008
你完全不正确。在这种情况下,与内置函数相比,滚动自己的速度提高了 10 倍,因为当您自己执行时,您可以信任 FPU 标志的状态,这是内置_ftol无法做到的,或者您可以使用 SSE 并行执行。
3赞 akauppi 3/15/2009
或者,您可以标记“-msse3”(gcc),并让“固定”FTSTTP 无缝地正确执行。
0赞 Nick Dowell 4/7/2011
编译器提供的例程不太适合性能至关重要的多媒体应用程序
7赞 akauppi 9/17/2008 #6

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
8赞 Suma 9/23/2008 #7

对于纯 x86/x87 代码,一个常用的技巧是强制浮点数的尾数部分表示整数。

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;
}

评论

2赞 chmike 5/1/2009
对于无符号整数,它可以更简单: inline uint32_t toInt( float fval ) { static float const snapper = 1<<23; fval += snapper; return ((uint32_t)fval) & 0x007FFFFF; }
0赞 R.. GitHub STOP HELPING ICE 11/25/2010
static float const snapper;使这比必要的速度慢。只需编写fval += 1<<23;
3赞 Suma 11/25/2010
在 x86 上,它并不慢,因为生成的代码是相同的。在 x87 上没有 FPU 指令立即进行参数。
7赞 akauppi 3/15/2009 #8

如果您能保证运行代码的 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 不同(FISTP 有其问题,导致速度缓慢)。它是 SSE3 的一部分,但实际上是(唯一的)X87 端改进。

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

支持 SSE3 的处理器

3赞 Jan 6/1/2013 #9

由于 MS 将我们从 X64 中的内联汇编中剔除并迫使我们使用内部函数,因此我查找了要使用的内部函数。MSDN 文档给出了一个示例。_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);

其中四舍五入到最接近是默认的(无论如何)。

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

而且,不,这在 32 位中不起作用,因此请继续使用 FISTP 指令:

_asm fld d
_asm fistp i

评论

0赞 Cody Gray - on strike 7/20/2013
这很有趣,并且似乎是正确的,但在我的测试中,x64 编译器实际上为您的代码和 MSDN 示例生成了完全相同的代码(使用反汇编程序进行验证)。
5赞 the swine 2/27/2014 #10

我假设截断是必需的,就像用“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 控制字设置为不同的舍入模式来实现。

评论

1赞 PhiS 2/27/2014
内联汇编:是的,Embarcadero(以前称为Borland)确实支持它(C++和Delphi编译器都支持)