提问人:robottobor 提问时间:9/17/2008 最后编辑:kristianprobottobor 更新时间:12/15/2014 访问量:30161
在 x86 上将 float 转换为 int 的最快方法是什么
What is the fastest way to convert float to int on x86
问:
您知道在 x86 CPU 上将浮点数转换为 int 的最快方法是什么。优选在 C 或组件中(可以在 C 中内联),用于以下任意组合:
- 32/64/80 位浮点数 -> 32/64 位整数
我正在寻找一些比让编译器做更快的技术。
答:
使用 SSE 进行打包转换是迄今为止最快的方法,因为您可以在同一指令中转换多个值。ffmpeg 为此有很多汇编(主要用于将音频的解码输出转换为整数样本);检查一些示例。
评论
在程序集中,有一条指令可以将浮点数转换为 int:使用 FISTP 指令。它从浮点堆栈中弹出值,将其转换为整数,然后存储在指定的地址。我认为没有更快的方法(除非您使用我不熟悉的 MMX 或 SSE 等扩展指令集)。
另一个指令 FIST 将值保留在 FP 堆栈上,但我不确定它是否适用于四字大小的目的地。
如果你真的关心这个速度,请确保你的编译器正在生成FIST指令。在 MSVC 中,可以使用 /QIfist 执行此操作,请参阅此 MSDN 概述
您还可以考虑使用 SSE 内部函数来为您完成这项工作,请参阅英特尔的这篇文章:http://softwarecommunity.intel.com/articles/eng/2076.htm
这取决于您是想要截断转换还是舍入转换以及精度。默认情况下,当您从 float 到 int 时,C 将执行截断转换。有 FPU 指令可以做到这一点,但它不是 ANSI C 转换,使用它有重要的注意事项(例如了解 FPU 舍入状态)。由于你的问题的答案非常复杂,并且取决于你没有表达的一些变量,所以我推荐这篇文章:
http://www.stereopsis.com/FPU.html
通常,您可以相信编译器是高效且正确的。为编译器中已存在的内容滚动自己的函数通常不会获得任何好处。
评论
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
对于纯 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;
}
评论
static float const snapper;
使这比必要的速度慢。只需编写fval += 1<<23;
如果您能保证运行代码的 CPU 与 SSE3 兼容(甚至 Pentium 5 也兼容 JBB),则可以允许编译器使用其 FISTTP 指令(即 gcc 的 -msse3)。它似乎一直在做它应该做的事情:
请注意,FISTTP 与 FISTP 不同(FISTP 有其问题,导致速度缓慢)。它是 SSE3 的一部分,但实际上是(唯一的)X87 端改进。
无论如何,其他 X86 CPU 可能会很好地进行转换。:)
由于 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
评论
我假设截断是必需的,就像用“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 控制字设置为不同的舍入模式来实现。
评论