Domanda

Che cosa è il modo più veloce è sapere per convertire un numero in virgola mobile in un int su una CPU x86.Preferibilmente in C o assembly (che può essere in-foderato in C) per una qualsiasi combinazione dei seguenti:

  • 32/64/80-bit float -> 32/64-bit integer

Sto cercando qualche tecnica che è più veloce di quella di lasciare che il compilatore di farlo.

È stato utile?

Soluzione

Dipende se vuoi un troncamento di conversione o un arrotondamento uno, e alla quale precisione.Per impostazione predefinita, C si esibirà in un troncamento di conversione quando si va da float a int.Ci sono istruzioni FPU che fare, ma non è ANSI C conversione e ci sono importanti avvertenze per l'utilizzo (ad esempio, sapendo che la FPU arrotondamento dello stato).Dal momento che la risposta al tuo problema è molto complesso e dipende da alcune variabili non hai espresso, ti consiglio anche questo articolo sul tema:

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

Altri suggerimenti

Imballato conversione utilizzando la SSE è di gran lunga il metodo più veloce, dal momento che è possibile convertire più valori, nella stessa istruzione. ffmpeg ha un sacco di montaggio per questo (soprattutto per la conversione di decodificato uscita audio, per l'intero campioni);controllare per vedere alcuni esempi.

Comunemente usato il trucco per la pianura x86/87 x codice è per forza la mantissa parte del galleggiante per rappresentare int.Versione a 32 bit segue.

La versione a 64 bit è di tipo analogico.La versione Lua postato sopra è più veloce, ma si basa sul troncamento del doppio di un 32-bit di risultato, pertanto si richiede l'87 x unità da impostare a doppia precisione, e non può essere adattato per double a int 64-bit di conversione.

La cosa bella di questo codice è completamente portatile per tutte le piattaforme conforme a IEEE 754, l'unico presupposto è il punto mobile di arrotondamento è impostata la modalità più vicino.Nota:Portatile, nel senso che compila e funziona.Piattaforme diverse da x86 di solito non trarre vantaggio da questa tecnica, se non del tutto.

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

Se si è in grado di garantire la CPU esegue il codice è SSE3 compatibile (anche Pentium 5, JBB), è possibile consentire al compilatore di utilizzare la sua FISTTP istruzione (es.-msse3 per gcc).Sembra di fare le cose come si deve è sempre stato fatto:

http://software.intel.com/en-us/articles/how-to-implement-the-fisttp-streaming-simd-extensions-3-instruction/

Nota che FISTTP è diverso da FISTP (che ha i suoi problemi, causando lentezza).Si tratta di una parte di SSE3, ma in realtà è (l'unico) 87 X lato raffinatezza.

Altri poi X86 CPU sarebbe probabilmente fare la conversione bene, comunque.:)

I processori con supporto SSE3

C'è una sola istruzione per la conversione in virgola mobile a un int in assemblea:utilizzare il FISTP istruzione.Si apre il valore off stack a virgola mobile, si converte un numero intero, e poi negozi, all'indirizzo specificato.Io non credo che ci sarebbe un modo più veloce (a meno che non si utilizza esteso set di istruzioni come MMX o SSE, che io non sono a conoscenza).

Un'altra istruzione, PUGNO, lascia il valore di FP stack, ma non sono sicuro che funziona con processore quad-parola di dimensioni destinazioni.

Il codice Lua di base è il seguente snippet di codice per fare questo (check-in src/luaconf.h da www.lua.org).Se si trova (COSÌ trova) un modo più veloce, sono sicuro che sarei entusiasta.

Oh, lua_Number significa il doppio.:)

/*
@@ 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

Presumo che il troncamento è richiesto, lo stesso come se uno scrive i = (int)f in "C".

Se si dispone di SSE3, è possibile utilizzare:

int convert(float x)
{
    int n;
    __asm {
        fld x
        fisttp n // the extra 't' means truncate
    }
    return n;
}

In alternativa, con set di istruzioni SSE2 (o x64 dove inline assembly potrebbero non essere disponibili), è possibile utilizzare quasi veloce:

#include <xmmintrin.h>
int convert(float x)
{
    return _mm_cvtt_ss2si(_mm_load_ss(&x)); // extra 't' means truncate
}

Sui computer più vecchi c'è un'opzione per impostare la modalità di arrotondamento manualmente e di eseguire la conversione ordinaria fistp istruzione.Che probabilmente funzionano solo per le matrici dei carri allegorici, in caso contrario la cura deve essere presa per non utilizzare costrutti che renderebbe il compilatore cambiare la modalità di arrotondamento (come la fusione).Si è fatto simile a questo:

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

Nota che l'assembly inline funziona solo con Microsoft Visual Studio compilatori (e forse Borland), dovrebbe essere riscritto per GNU assemblea, al fine di compilare con gcc.Il set di istruzioni SSE2 soluzione intrinseci dovrebbe essere abbastanza portatile, tuttavia.

Altre modalità di arrotondamento sono possibili diverse SSE2 intrinseci o impostando manualmente l'FPU parola di controllo per una diversa modalità di arrotondamento.

Se davvero la cura per la velocità di questo assicurarsi che il compilatore genera il PUGNO di istruzioni.In MSVC si può fare questo con /QIfist, vedere questo MSDN panoramica

Si può anche considerare l'utilizzo di SSE intrinseci per fare il lavoro per voi, vedere questo articolo da Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm

Dato che MS scews di assembly inline in X64 e ci costringe a utilizzare intrinseci, ho guardato su quale utilizzare. MSDN doc_mm_cvtsd_si64x con un esempio.

L'esempio funziona, ma è terribilmente inefficiente, utilizzando unaligned di carico di 2 doppie, in cui abbiamo bisogno solo di un singolo carico, in modo da sbarazzarsi dell'ulteriore requisito di allineamento.Poi un sacco di inutili carichi e ricarica sono prodotte, ma non può essere eliminato come segue:

 #include <intrin.h>
 #pragma intrinsic(_mm_cvtsd_si64x)
 long long _inline double2int(const double &d)
 {
     return _mm_cvtsd_si64x(*(__m128d*)&d);
 }

Risultato:

        i=double2int(d);
000000013F651085  cvtsd2si    rax,mmword ptr [rsp+38h]  
000000013F65108C  mov         qword ptr [rsp+28h],rax  

La modalità di arrotondamento può essere impostato senza assembly inline, ad es.

    _control87(_RC_NEAR,_MCW_RC);

dove arrotondamento al più vicino è a default (in ogni caso).

La domanda se impostare la modalità di arrotondamento ad ogni chiamata o per scontato che sarà restaurato (anche di terze parti libs), dovrà essere risolta da esperienza, credo.È necessario includere float.h per _control87() e le relative costanti.

E, no, non funziona a 32 bit, in modo da mantenere utilizzando il FISTP istruzione:

_asm fld d
_asm fistp i

In generale, ci si può fidare che il compilatore sia efficiente e corretto.Di solito c'è nulla da guadagnare inserendo le proprie funzioni per un qualcosa che già esiste nel compilatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top