2012-02-18 22 views
9

Estoy buscando una manera de truncar un float en un int de una manera rápida y portátil (IEEE 754). La razón se debe a que en esta función el 50% del tiempo se gasta en el reparto:Conversión rápida de float a int (truncado)

float fm_sinf(float x) { 
    const float a = 0.00735246819687011731341356165096815f; 
    const float b = -0.16528911397014738207016302002888890f; 
    const float c = 0.99969198629596757779830113868360584f; 

    float r, x2; 
    int k; 

    /* bring x in range */ 
    k = (int) (F_1_PI * x + copysignf(0.5f, x)); /* <-- 50% of time is spent in cast */ 

    x -= k * F_PI; 

    /* if x is in an odd pi count we must flip */ 
    r = 1 - 2 * (k & 1); /* trick for r = (k % 2) == 0 ? 1 : -1; */ 

    x2 = x * x; 

    return r * x*(c + x2*(b + a*x2)); 
} 
+1

¿Has intentado compilar con '-ffast-math'? O omita la función copysign y use 'lrint()' en lugar de un (int) cast – hirschhornsalz

+1

Sus consensos son innecesariamente (o demasiado optimistas) precisos. La precisión individual IEEE754 solo es válida para 6 cifras significativas, la precisión doble es válida para 15 dígitos y el doble largo varía entre compiladores y arquitecturas, pero incluso en las FPU x86, el formato nativo de 80 bits solo es válido para 20 dígitos. Si necesita ese nivel de precisión, el código no funcionará en ningún caso, y una biblioteca de precisión arbitraria sería * mucho * más lenta. – Clifford

+1

@Clifford: Sé que son demasiado precisos, siempre me gusta calcular 35 dígitos para que pueda soportar cualquier cosa hasta 128 bits con solo copiar/pegar. – orlp

Respuesta

2

para ser portátil tendría que añadir algunas directivas y aprender idiomas pareja ensamblador pero podría teóricamente podría utilizar algo de ensamblador en línea para mover partes del registro de punto flotante a eax/rax ebx/rbx y convertir lo que necesitaría a mano, la especificación de coma flotante es un dolor en el trasero, pero estoy bastante seguro de que si lo hace con el ensamblaje estará mucho más rápido, ya que sus necesidades son muy específicas y el método del sistema es probablemente más genérico y menos eficiente para su propósito

+3

¿Qué te hace pensar que la piratería bit a bit en ensamblador será más rápida que las instrucciones FP nativas (supongo x86 aquí) para convertir de float a entero? –

+0

@OliCharlesworth De hecho, hay un truco para hacer esto de manera muy eficiente utilizando los intrínsecos de SSE si está dispuesto a poner algunas restricciones en la entrada. La razón por la cual el lanzamiento es tan lento es porque el lenguaje requiere que la salida sea correcta para todas las entradas. – Mysticial

+0

ha, mysticial me ganó. – Ryan

0

Puede omitir la conversión a int altoge o bien usando frexpf para obtener la mantisa y el exponente, e inspeccione la mantisa en bruto (use un union) en la posición de bit apropiada (calculada usando el exponente) para determinar (el cuadrante depende) r.

+0

Masa Currie: Lo siento muchísimo, en mi prisa me olvidé de copiar una línea de mi función. También uso el valor de 'int' para obtener un' fmod' gratis. 'fmod' en sí era demasiado lento. – orlp

+0

@nightcracker: ¿Has probado 'nearbyint()' para esa parte? Convertir a entero y volver al punto flotante de nuevo va a ser lento. – caf

+0

@caf: en realidad, este último es muy rápido. Solo la conversión a entero es muy lenta. – orlp

4

La lentitud de los moldes float-> int se produce principalmente cuando se utilizan instrucciones FPU x87 en x86. Para realizar el truncamiento, el modo de redondeo en la palabra de control de FPU debe cambiarse a redondeo a cero y viceversa, que tiende a ser muy lento.

Al usar SSE en lugar de las instrucciones x87, hay un truncamiento disponible sin cambios de palabra de control. Puede hacerlo utilizando las opciones del compilador (como -mfpmath=sse -msse -msse2 en GCC) o compilando el código como de 64 bits.

El conjunto de instrucciones SSE3 tiene la instrucción FISTTP para convertir a entero con truncamiento sin cambiar la palabra de control. Un compilador puede generar esta instrucción si se le indica que asuma SSE3.

Alternativamente, la función C99 lrint() se convertirá en un número entero con el modo de redondeo actual (redondeado a más cercano a menos que lo haya cambiado). Puede usar esto si elimina el término copysignf. Lamentablemente, esta función aún no es omnipresente después de más de diez años.