x86 CPU で浮動小数点数を int に変換する最も速い方法は何ですか。次の任意の組み合わせについては、Cまたはアセンブリ(Cでインライン化できる)であることが望ましい:
- 32/64/80 ビット浮動小数点 -> 32/64 ビット整数
コンパイラーに任せるよりも速いテクニックを探しています。
x86 CPU で浮動小数点数を int に変換する最も速い方法は何ですか。次の任意の組み合わせについては、Cまたはアセンブリ(Cでインライン化できる)であることが望ましい:
コンパイラーに任せるよりも速いテクニックを探しています。
切り捨て変換または丸め変換が必要かどうか、およびどのような精度で行うかによって異なります。デフォルトでは、C は、float から int に移動するときに切り捨て変換を実行します。それを行う FPU 命令がありますが、これは ANSI C 変換ではなく、使用には重大な注意事項があります (FPU の丸め状態を知るなど)。あなたの問題に対する答えは非常に複雑であり、あなたが表現していないいくつかの変数に依存するため、この問題に関する次の記事をお勧めします。
SSE を使用したパック変換は、同じ命令で複数の値を変換できるため、最も高速な方法です。 ffmpegには、このための多くのアセンブリがあります (ほとんどの場合、オーディオのデコードされた出力を整数サンプルに変換するためのものです)。いくつかの例を確認してください。
プレーンな x86/x87 コードで一般的に使用されるトリックは、float の仮数部分が 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;
}
コードを実行する CPU が SSE3 互換であることを保証できる場合 (Pentium 5 でさえ JBB)、コンパイラが FISTTP 命令 (つまり、gcc の場合は -msse3) を使用できるようにすることができます。常に行われるべきだったようなことをしているようです:
FISTTP は FISTP とは異なることに注意してください (これには問題があり、速度が低下します)。これは SSE3 の一部として提供されますが、実際には (唯一の) X87 側の改良版です。
とにかく、それ以外の X86 CPU はおそらく問題なく変換を行うでしょう。:)
アセンブリで浮動小数点を int に変換する命令が 1 つあります。FISTP 命令を使用します。浮動小数点スタックから値をポップし、整数に変換してから、指定されたアドレスに格納します。これより速い方法はないと思います (MMX や SSE などの拡張命令セットを使用しない限り、私はよく知りません)。
別の命令 FIST は値を FP スタックに残しますが、それがクワッド ワード サイズのデスティネーションで機能するかどうかはわかりません。
Lua コード ベースには、これを行うための次のスニペットがあります (www.lua.org の src/luaconf.h を確認してください)。あなたがもっと速い方法を見つけたら (SO finds)、きっと彼らは興奮するでしょう。
ああ、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
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) でのみ機能することに注意してください。gcc でコンパイルするには、GNU アセンブリに書き直す必要があります。ただし、組み込み関数を使用した SSE2 ソリューションは移植性が高いはずです。
他の丸めモードは、別の SSE2 組み込み関数によって、または FPU 制御ワードを別の丸めモードに手動で設定することによって可能です。
この速度が本当に気になる場合は、コンパイラが FIST 命令を生成していることを確認してください。MSVC では、/QIfist を使用してこれを行うことができます。この MSDN の概要を参照してください。
SSE 組み込み関数を使用して作業を行うことも検討できます。Intel の次の記事を参照してください: http://softwarecommunity.intel.com/articles/eng/2076.htm
一般に、コンパイラは効率的で正確であると信頼できます。通常、コンパイラーに既に存在するものに対して独自の関数をローリングしても、得られるものは何もありません。