2008-09-17 15 views
20

¿Cuál es la forma más rápida de convertir un número de punto flotante en int en una CPU x86? Preferiblemente en C o montaje (que puede ser en forrado en C) para cualquier combinación de los siguientes:¿Cuál es la forma más rápida de convertir float a int en x86?

  • 32/64/80-bit float -> 32/64 bits número entero

Estoy buscando alguna técnica que sea más rápida que simplemente dejar que el compilador lo haga.

+0

Cambiar de un Pentium 5 a un chip que hace matemáticas bien ... (Hombre que me hace sentir viejo ...) – JBB

+0

Estoy rodando por el suelo. Dang - ¡es una lástima que la gente te modifique por eso! – Kevin

+0

Valió la pena. :) – JBB

Respuesta

16

Depende de si desea una conversión truncar o un redondeo y con qué precisión. De forma predeterminada, C realizará una conversión de truncamiento cuando pase de float a int. Hay instrucciones de FPU que lo hacen, pero no es una conversión ANSI C y existen importantes advertencias para su uso (como conocer el estado de redondeo FPU). Dado que la respuesta a su problema es bastante complejo y depende de algunas variables que no han expresado, recomiendo este artículo sobre el tema:

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

12

La conversión empaquetada utilizando SSE es, con mucho, el método más rápido, ya que puede convertir múltiples valores en la misma instrucción. ffmpeg tiene mucho ensamblaje para esto (principalmente para convertir la salida decodificada de audio en muestras enteras); compruébalo para ver algunos ejemplos.

+0

Es una buena sugerencia; sin embargo, lo advertiré diciendo que asume dos cosas: - Que tiene un procesador x86 con SSE (> PII) o SSE2 (> PIII) - Que de hecho quiere un truncamiento, no un redondeo, conversión –

+0

También tenga en cuenta la limitación de que esto, por supuesto, no será una opción para un valor de coma flotante de 80 bits – PhiS

6

Hay una instrucción para convertir un punto flotante a un int en el ensamblaje: use la instrucción FISTP. Muestra el valor de la pila de coma flotante, lo convierte en un número entero y luego lo almacena en la dirección especificada. No creo que haya una manera más rápida (a menos que uses conjuntos de instrucciones extendidas como MMX o SSE, con los que no estoy familiarizado).

Otra instrucción, FIST, deja el valor en la pila de FP pero no estoy seguro si funciona con destinos de cuatro palabras.

-7

En general, puede confiar en que el compilador para ser eficiente y correcta. Por lo general, no se gana nada ejecutando sus propias funciones para algo que ya existe en el compilador.

+2

. Simplemente es incorrecto. En este caso rodar uno mismo es una mejora de velocidad 10x muy demostrable sobre las funciones integradas porque cuando lo haces tú mismo puedes confiar en el estado de las banderas de FPU, lo que no funciona en _ftol, o puedes hacerlo paralelizado usando SSE. –

+2

O puede marcar '-msse3' (gcc) y hacer que el 'fijo' FTSTTP lo haga bien, sin problemas. – akauppi

+0

Las rutinas compilador suministrado no son muy adecuadas para aplicaciones multimedia donde el rendimiento es crucial para –

6

La base de código Lua tiene el siguiente fragmento de código (verifique src/luaconf.h desde www.lua.org). Si encuentra (SO encuentra) una manera más rápida, estoy seguro de que estarían encantados.

Oh, lua_Number significa doble. :)

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

Un truco utilizado comúnmente para el código de llanura x86/x87 es forzar a la parte de mantisa del flotador para representar el int. Sigue la versión de 32 bits.

La versión de 64 bits es analógica. La versión de Lua publicada anteriormente es más rápida, pero se basa en el truncamiento de doble a un resultado de 32 bits, por lo tanto, requiere que la unidad x87 se establezca en doble precisión y no se puede adaptar para una conversión de doble a 64 bits.

Lo bueno de este código es que es completamente portátil para todas las plataformas conforme a IEEE 754, la única suposición que se hace es que el modo de redondeo de punto flotante se establece más cerca. Nota: Portable en el sentido de que compila y funciona. Las plataformas que no sean x86 usualmente no se benefician mucho de esta técnica, si es que lo hacen.

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

entero sin signo que puede ser más simple: línea uint32_t Toint (FVAL float) { estática pargo float const = 1 << 23; fval + = pargo; return (* (uint32_t *) fval) & 0x007FFFFF; } – chmike

+0

'flotador estático const snapper;' lo hace más lento de lo necesario. Simplemente escriba 'fval + = 1 << 23;' –

+3

En x86 no es más lento, ya que el código generado es el mismo. No hay instrucciones de FPU que tengan argumentos inmediatos sobre x87. – Suma

6

Si se puede garantizar el funcionamiento de la CPU su código es compatible con SSE3 (incluso Pentium 5 es, JBB), puede permitir al compilador que utilice su instrucción FISTTP (es decir -msse3 para GCC). Parece que hacer lo que debería haber sido hecho siempre:

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

Tenga en cuenta que es diferente de FISTTP FISTP (que tiene sus problemas, causando la lentitud). Viene como parte de SSE3 pero en realidad es (el único) refinamiento del lado X87.

Aparte de eso, la CPU X86 probablemente haría la conversión bien, de todos modos. :)

Processors with SSE3 support

3

Desde MS nos scews de ensamblado en línea en X64 y nos obliga a utilizar los intrínsecos, miré hacia arriba, que va a utilizar. MSDN doc da _mm_cvtsd_si64x con un ejemplo.

El ejemplo funciona, pero es terriblemente ineficiente, con una carga desalineada de 2 dobles, donde solo necesitamos una carga, por lo que se elimina el requisito de alineación adicional. A continuación, una gran cantidad de cargas innecesarias y recargas se producen, pero que puede ser eliminado de la siguiente manera:

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

Resultado:

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

El modo de redondeo se puede ajustar sin montaje en línea, por ejemplo,

_control87(_RC_NEAR,_MCW_RC); 

donde el redondeo al más cercano es por defecto (de todos modos).

La pregunta si establecer el modo de redondeo en cada llamada o asumir que se restaurará (libs de terceros) tendrá que ser respondida por la experiencia, supongo. Deberá incluir float.h para _control87() y constantes relacionadas.

Y, no, esto no va a funcionar en 32 bits, por lo que seguir usando la instrucción FISTP:

_asm fld d 
_asm fistp i 
+0

Esto es interesante, y parece ser correcto, pero en mis pruebas el compilador x64 realmente genera el * código exacto * (verificado usando un desensamblador) para su código aquí y el ejemplo de MSDN. –

2

Asumo se requiere truncamiento, igual que si uno escribe i = (int)f en "C".

Si tiene SSE3, puede utilizar:

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

Alternativamente, con SSE2 (o en x64, donde ensamblado en línea podría no estar disponible), se puede utilizar casi tan rápido:

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

En las computadoras más antiguas, hay una opción para configurar el modo de redondeo de forma manual y realizar la conversión utilizando la instrucción ordinaria fistp.Probablemente esto solo funcionará para matrices de flotadores, de lo contrario se debe tener cuidado de no utilizar ninguna construcción que haga que el compilador cambie el modo de redondeo (como el fundido). Se realiza así:

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

Tenga en cuenta que el ensamblaje en línea sólo funciona con los compiladores de Visual Studio de Microsoft (y tal vez de Borland), que tendría que ser reescrito para el montaje de GNU con el fin de compilar con gcc. Sin embargo, la solución SSE2 con intrínsecos debería ser bastante portátil.

Otros modos de redondeo son posibles por diferentes intrínsecos SSE2 o manualmente ajustando la palabra de control FPU a un modo de redondeo diferente.

+0

re montaje en línea: sí Embarcadero (anteriormente Borland) sí lo admite (tanto los compiladores C++ como Delphi lo hacen) – PhiS

Cuestiones relacionadas