25

x86 CPU で浮動小数点数を int に変換する最も速い方法は何ですか。次の任意の組み合わせについては、Cまたはアセンブリ(Cでインライン化できる)であることが望ましい:

  • 32/64/80 ビット浮動小数点 -> 32/64 ビット整数

コンパイラーに任せるよりも速いテクニックを探しています。

4

10 に答える 10

18

切り捨て変換または丸め変換が必要かどうか、およびどのような精度で行うかによって異なります。デフォルトでは、C は、float から int に移動するときに切り捨て変換を実行します。それを行う FPU 命令がありますが、これは ANSI C 変換ではなく、使用には重大な注意事項があります (FPU の丸め状態を知るなど)。あなたの問題に対する答えは非常に複雑であり、あなたが表現していないいくつかの変数に依存するため、この問題に関する次の記事をお勧めします。

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

于 2008-09-17T00:34:40.867 に答える
14

SSE を使用したパック変換は、同じ命令で複数の値を変換できるため、最も高速な方法です。 ffmpegには、このための多くのアセンブリがあります (ほとんどの場合、オーディオのデコードされた出力を整数サンプルに変換するためのものです)。いくつかの例を確認してください。

于 2008-09-17T00:24:10.407 に答える
8

プレーンな 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;
}
于 2008-09-23T07:19:57.773 に答える
7

コードを実行する 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 とは異なることに注意してください (これには問題があり、速度が低下します)。これは SSE3 の一部として提供されますが、実際には (唯一の) X87 側の改良版です。

とにかく、それ以外の X86 CPU はおそらく問題なく変換を行うでしょう。:)

SSE3 をサポートするプロセッサー

于 2009-03-15T11:34:23.703 に答える
7

アセンブリで浮動小数点を int に変換する命令が 1 つあります。FISTP 命令を使用します。浮動小数点スタックから値をポップし、整数に変換してから、指定されたアドレスに格納します。これより速い方法はないと思います (MMX や SSE などの拡張命令セットを使用しない限り、私はよく知りません)。

別の命令 FIST は値を FP スタックに残しますが、それがクワッド ワード サイズのデスティネーションで機能するかどうかはわかりません。

于 2008-09-17T00:27:00.557 に答える
7

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
于 2008-09-17T01:05:58.387 に答える
5

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 制御ワードを別の丸めモードに手動で設定することによって可能です。

于 2014-02-26T17:28:59.860 に答える
3

この速度が本当に気になる場合は、コンパイラが FIST 命令を生成していることを確認してください。MSVC では、/QIfist を使用してこれを行うことができます。この MSDN の概要を参照してください。

SSE 組み込み関数を使用して作業を行うことも検討できます。Intel の次の記事を参照してください: http://softwarecommunity.intel.com/articles/eng/2076.htm

于 2008-09-17T00:29:51.783 に答える
-8

一般に、コンパイラは効率的で正確であると信頼できます。通常、コンパイラーに既に存在するものに対して独自の関数をローリングしても、得られるものは何もありません。

于 2008-09-17T00:35:57.587 に答える