Che cosa è il modo più veloce per convertire float a int su x86
-
09-06-2019 - |
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.
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:
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:
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.:)
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 dà _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.