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