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

在x86 CPU上将浮点数转换为int最快的方法是什么? 对于以下的任何组合,最好用C语言或汇编(可以用C语言表示):

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

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

这取决于你想要一个截断的转换还是一个舍入的转换,并且以什么精度。 默认情况下,当你从float到int时,C将执行截断转换。 有FPU指令这样做,但它不是一个ANSI C转换,并有使用它的重大警告(如知道FPU舍入状态)。 由于你的问题的答案是相当复杂的,取决于你没有expression的一些variables,我推荐这篇文章的问题:

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

使用SSE进行打包转换是目前为止最快的方法,因为您可以在同一个指令中转换多个值。 ffmpeg有很多程序集(主要用于将audio的解码输出转换为整数采样); 检查它的一些例子。

对于普通的x86 / x87代码来说,一个常用的技巧是强制float的尾数部分来表示int。 32位版本如下。

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

关于这个代码的好处是它对于符合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; } 

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

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

如果你能保证运行你的代码的CPU是SSE3兼容的(甚至奔腾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可能会做转换就好了,反正。 🙂

处理器支持SSE3

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 

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

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

由于MS使我们脱离了X64的内联汇编,迫使我们使用内在函数,所以我查了一下要使用哪一个。 MSDN文档给出了_mm_cvtsd_si64x的例子。

这个例子可以工作,但是效率非常低,使用2倍的未alignment的负载,我们只需要一个负载,所以摆脱了额外的alignment要求。 然后产生很多不必要的加载和重新加载,但是它们可以被消除如下:

  #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 

我假定需要截断,就像在“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 } 

在较旧的计算机上,可以select手动设置舍入模式,并使用普通的fistp指令执行转换。 这可能只适用于浮点数组,否则必须小心不要使用任何会使编译器改变舍入模式的构造(如cast)。 它是这样做的:

 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),为了使用gcc进行编译,必须将其重写为GNU汇编程序。 具有固有特性的SSE2解决scheme应该是相当便携的,但是。

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

一般来说,你可以相信编译器是高效和正确的。 对于编译器中已经存在的东西,自己编写的函数通常没有什么好处。