2008-09-17 13 views
20

x86 CPU上で浮動小数点数を整数に変換する方法を知っている最速の方法は何ですか?できれ以下の任意の組み合わせのために(すなわち、Cででライニングすることができる)、Cまたはアセンブリにおいて:浮動小数点数をx86のintに変換する最も速い方法

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

私はコンパイラにそれをさせるよりも速いいくつかのテクニックを探しています。

+0

ペンティアム5から数学的に正しいチップに切り替える... (私に古い気分にさせる男...) – JBB

+0

私は地面を転がり回っています。ダング - それはあなたのためにあなたをダウンmodded悪いです! – Kevin

+0

それは価値があった。 :) – JBB

答えて

16

それが依存するあなたは切り捨て、変換をしたい場合や1つの丸めとどんな精度で。デフォルトでは、floatからintに移動すると、Cは切り捨て変換を実行します。それを行うFPU命令がありますが、ANSI C変換ではなく、FPUの丸め状態を知るなどの重要な注意点があります。あなたの問題への答えは非常に複雑であり、あなたが表現されていないいくつかの変数に依存しているので、私はこの問題について、この記事をお勧めします。

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

12

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

+0

これは良い提案ですが、私はそれが2つのことを前提にしていると言います。 - あなたはSSE(> PII)またはSSE2(> PIII)を持つx86プロセッサを持っていることをお伝えします - 実際には、丸め、変換 –

+0

これはもちろん80ビットの浮動小数点値のオプションではないことに注意してください – PhiS

6

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

FISTの別の命令では、FPスタックに値が残っていますが、クワッドワードサイズのデスティネーションでは正しく動作しません。

3

この速度が本当に気になるのなら、コンパイラがFIST命令を生成していることを確認してください。 MSVCでは、あなたが/ QIfist、see this MSDN overview

でこれを行うことができますが、インテルからこの記事を参照して、あなたのための仕事をするためにSSEの組み込み関数の使用を検討することができます:http://softwarecommunity.intel.com/articles/eng/2076.htm

-7

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

+2

あなたは単に間違っています。この場合、組み込みの関数よりも10倍の速度向上が見られます。自分で実行すると、_ftolに組み込まれていないFPUフラグの状態を信頼することができます。 SSE。 –

+2

また、 '-msse3'(gcc)にフラグを立てて、 'fixed' FTSTTPでシームレスに行うこともできます。 – akauppi

+0

コンパイラが提供するルーチンは、性能は、符号なし整数のが簡単であることができる –

6

ルアコードベースには、これを行うためのスニペットがあります(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 
9

普通のx86/x87コードでよく使われるトリックは、浮動小数点数の仮数部をintにすることです。 32ビット版が続きます。

64ビットバージョンは類似しています。上記のLuaバージョンは高速ですが、32ビットの結果を2倍に切り捨てることに依存しているため、倍精度に設定する必要があり、2倍から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; 
} 
+2

重要であるマルチメディアアプリケーションに適していない。オーバーライド:(フロートFVAL)= 1 << 23 { 静的フロートCONSTスナッパのuint32_t インライン。 fval + = snapper; return(*(uint32_t *)fval)&0x007FFFFF; } – chmike

+0

'static float const snapper;'これは必要以上に遅くなります。 'fval + = 1 << 23;' –

+3

と書くだけです。生成されるコードは同じであるため、x86では遅くはありません。 x87に即座に引数をとるFPU命令はありません。 – Suma

6

あなたのコードを実行している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はおそらく変換をうまくいくでしょう。 :)

Processors with SSE3 support

3

MSはX64でインラインアセンブリから私たちをscewsと組み込み関数を使用するために私たちを強制しているので、私が使用する見上げました。 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); 

ここで、四捨五入はデフォルトです(とにかく)。

呼び出しごとに丸めモードを設定するか、それが復元されると仮定するか(第三者のlibs)は、経験によって回答する必要があります。 _control87()とそれに関連する定数にはfloat.hを含める必要があります。

そして、いや、これは32ビットで動作するので、FISTP命令を使い続けないであろう。一つは「C」でi = (int)fを書き込んかのように

_asm fld d 
_asm fistp i 
+0

これは興味深く、正しいと思われますが、私のテストではx64コンパイラは実際にコードとMSDNの例で正確に同じコード*(逆アセンブラを使用して検証済み)を生成します。 –

2

私は同じ、切り捨てが必要とされているとします。

あなたは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命令を使用して変換を実行するオプションがあります。これはおそらく浮動小数点数の配列に対してのみ機能します。そうでなければ、コンパイラーが丸めモードを変更するようなコンストラクト(キャストなど)を使用しないように注意する必要があります。次のように行われます。インラインアセンブリは、MicrosoftのVisual Studioのコンパイラ(多分ボーランド)で動作します

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: 
    } 
} 

注こと、それはgccでコンパイルするためにGNUアセンブリに書き換えなければなりません。 しかし、組み込み関数を備えたSSE2ソリューションは移植性が高いはずです。

他の丸めモードは、異なるSSE2組み込み関数によって、またはFPU制御ワードを手動で別の丸めモードに設定することによって可能です。

+0

re-in-line assembly:はいEmbarcadero(旧Borland)はこれをサポートしています(C++コンパイラとDelphiコンパイラの両方) – PhiS

関連する問題