2010-07-08 20 views
27

Estaba leyendo hoy sobre researchers discovering that NVidia's Phys-X libraries use x87 FP vs. SSE2. Obviamente, esto no será óptimo para los conjuntos de datos paralelos donde la velocidad supera la precisión. Sin embargo, el autor del artículo continúa citando:Punto flotante doble extendido (80 bits) en x87, no SSE2: ¿no lo echamos de menos?

Intel comenzó a desalentar el uso de x87 con la introducción de la P4 a finales de 2000. AMD x87 obsoleto ya que el K8 en el año 2003, tal como se define x86-64 con SSE2 apoyo; El C7 de VIA es compatible con SSE2 desde 2005. En las versiones de 64 bits de Windows, x87 está en desuso para el modo de usuario y está prohibido por completo en modo kernel. Prácticamente todo el mundo en la industria ha recomendado SSE sobre x87 desde 2005 y no hay motivos para usar x87, a menos que el software tenga que ejecutarse en un Pentium integrado o 486.

Me lo pregunté. Sé que x87 utiliza dobles extendidos de 80 bits internamente para calcular valores, y SSE2 no. ¿Esto no le importa a nadie? Me parece sorprendente. Sé que cuando hago cálculos en puntos, líneas y polígonos en un avión, los valores pueden ser sorprendentemente incorrectos al hacer sustracciones, y las áreas pueden colapsar y las líneas se alias entre sí debido a la falta de precisión. Usar valores de 80 bits vs. valores de 64 bits podría ayudar, me imagino.

¿Es esto incorrecto? De lo contrario, ¿qué podemos usar para realizar operaciones de doble FP ampliadas si x87 se elimina?

+1

No es realmente una respuesta a su pregunta, pero personalmente estoy esperando que el formato binario IEEE 754 de 128 bits se convierta en la corriente principal. –

+0

@Mark - en serio, ¿qué está tomando tanto tiempo? AVX puede ser un estándar antes de que se publique ... – codekaizen

+1

[Este] (https://www.cs.uaf.edu/2012/fall/cs301/lecture/11_02_other_float.html) es una buena respuesta sobre cuál fue el motivo para desalentar x87. Y sí, los cálculos SSE son menos precisos, se ve claramente en los compiladores JIT modernos (en comparación con los compiladores tradicionales basados ​​en x87). –

Respuesta

21

El mayor problema con x87 es básicamente que todas las operaciones de registro se realizan en 80 bits, mientras que la mayoría de las personas solo usan flotantes de 64 bits (es decir, flotadores de precisión doble). Lo que sucede es que cargas un flotador de 64 bits en la pila x87 y se convierte a 80 bits. Realiza algunas operaciones sobre él en 80 bits, luego lo almacena de nuevo en la memoria, convirtiéndolo en 64 bits. Obtendrás un resultado diferente que si hubieras realizado todas las operaciones con solo 64 bits, y con un compilador de optimización puede ser muy impredecible la cantidad de conversiones por las que puede pasar un valor, por lo que es difícil verificar que estás obteniendo el " correcta "respuesta al hacer pruebas de regresión.

El otro problema, que solo importa desde el punto de vista de alguien que escribe ensamblaje (o indirectamente escribiendo ensamblado, en el caso de alguien que escribe un generador de código para un compilador), es que el x87 usa una pila de registro, mientras que SSE usa registros accesibles individualmente. Con x87 tienes un montón de instrucciones adicionales para manipular la pila, e imagino que Intel y AMD preferirían hacer que sus procesadores corran rápido con el código SSE que tratar de hacer que esas instrucciones extra de manipulación de pila x87 corran rápido.

BTW si tiene problemas con la inexactitud, deberá consultar el artículo "What every programmer should know about floating-point arithmetic" y, a continuación, utilizar una biblioteca matemática de precisión arbitraria (por ejemplo, GMP).

+6

La optimización de los compiladores es suficientemente mala, pero pruebe un JIT que tenga la capacidad de alinear métodos pequeños (y por lo tanto, variar el número de temps en memoria). A veces llamo a este método y obtengo una respuesta, a veces llamo al mismo método con los mismos argumentos y obtengo un resultado diferente, ¡dependiendo de si el JITter marcó la llamada o no! Esa fue una regresión divertida para rastrear. –

+0

Sí, ya veo, eso se complica con compiladores que hacen este tipo de elecciones, más aún cuando los compiladores JIT lo hacen. En cuanto a la precisión, actualmente escalo el número a [0..1] y elimino bits comunes para disminuir el ruido debido a bits que simplemente cancelan, y me imaginé que 80 bits me darían más espacio. Si bien es cierto, al parecer, los efectos secundarios son demasiado altos de un costo. Espero probarlo en hardware QP ... cada vez que aparece. – codekaizen

+0

@Joe White Si está usando Java y NECESITA exactamente los mismos resultados cada vez que hace matemáticas de coma flotante, investigue el uso de la palabra clave 'strictfp'. Esto obliga a las matemáticas a ser IEEE 754 y no a lo que sea que haga la plataforma nativa (x87 en la inteligencia 32b, por ejemplo). http://en.wikipedia.org/wiki/Strictfp – KitsuneYMG

2

La otra respuesta parece sugerir que usar una precisión de 80 bits es una mala idea, pero no lo es. A veces desempeña un papel vital para mantener a raya la imprecisión, ver p. las escrituras de W. Kahan.

Utilice siempre la aritmética intermedia de 80 bits si puede salirse con la suya en lo que respecta a la velocidad. Si eso significa que tienes que usar x87 maths, bueno, hazlo. El apoyo es omnipresente y mientras la gente siga haciendo lo correcto, seguirá siendo omnipresente.

+3

Aunque, irónicamente, la precisión intermedia de 64 bits (* no * precisión de 80 bits) del uso de los registros x87 de 80 bits puede dar lugar a resultados * menos * precisos para operaciones aritméticas simples en dobles regulares de 53 bits. Asumiendo el habitual modo de redondeo de redondeos a pares, la operación '1e16 + 2.9999' en valores binary64 de IEEE 754 da un resultado redondeado correctamente de' 10000000000000002.0' en una máquina que usa SSE2, pero un resultado incorrectamente redondeado de '10000000000000004.0 'cuando se usa x87 con FPU, la precisión no se altera con la precisión predeterminada de 64 bits, gracias al doble redondeo. –

+2

Hay algunos casos en que el uso de precisión doble para calcular x + y arrojaría un resultado con un error de redondeo de 1/2ulp, mientras que el uso de precisión extendida y la conversión a doble produciría un error de redondeo de 2049/4096pulgadas . Por otro lado, hay muchos más casos en los que usar la precisión extendida para calcular x + y + z arrojará un resultado preciso, mientras que usar "doble" arrojará un resultado que es * mucho * menos preciso, o en algunos casos solo Claro Incorrecto. – supercat

5

Para hacer un uso adecuado de la matemática de precisión extendida, es necesario que un lenguaje admita un tipo que se puede usar para almacenar el resultado de cálculos intermedios, y se puede sustituir por las expresiones que producen esos resultados.Por lo tanto, teniendo en cuenta:

void print_dist_squared(double x1, double y1, double x2, double y2) 
{ 
    printf("%12.6f", (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); 
} 

debe haber algún tipo que podría ser utilizado para capturar y reemplazar las sub-expresiones comunes x2-x1 y y2-y1, permitiendo que el código para volver a escribir como:

void print_dist_squared(double x1, double y1, double x2, double y2) 
{ 
    some_type dx = x2-x1; 
    some_type dy = y2-y1; 
    printf("%12.6f", dx*dx + dy*dy); 
} 

sin alterar la semántica del programa. Desafortunadamente, ANSI C no pudo especificar ningún tipo que se pudiera usar para some_type en plataformas que realizan cálculos de precisión extendida, y se volvió mucho más común culpar a Intel por la existencia de tipos de precisión extendida que culpar al soporte fallido de ANSI.

De hecho, los tipos extendida precisión tienen tanto valor en plataformas sin unidades de punto flotante como lo hacen en los procesadores x87, ya que en este tipo de procesadores de un cálculo como x + y + z implicaría los siguientes pasos:

  1. Desempaquetar la mantisa, exponente, y posiblemente signo de x en registros separados (exponente y signo puede a menudo "doble-litera")
  2. Desempaquetar y lo mismo.
  3. Desplazar a la derecha la mantisa del valor con el exponente inferior, si existe, y luego sumar o restar los valores.
  4. En caso de que xey tengan diferentes signos, desplace la mantisa hacia la izquierda hasta que el bit situado más a la izquierda sea 1 y ajuste el exponente adecuadamente.
  5. Vuelva a empaquetar el exponente y la mantisa en doble formato.
  6. Desempaquete el resultado temporal.
  7. Desempaquetar z.
  8. Desplazar a la derecha la mantisa del valor con el exponente inferior, si existe, y luego sumar o restar los valores.
  9. En caso de que el resultado anterior yz tuvieran signos diferentes, desplace la mantisa hacia la izquierda hasta que el bit situado más a la izquierda sea 1 y ajuste el exponente adecuadamente.
  10. Vuelva a empaquetar el exponente y la mantisa en doble formato.

El uso de un tipo de precisión extendida permitirá eliminar los pasos 4, 5 y 6. Como una mantisa de 53 bits es demasiado grande para caber en menos de cuatro registros de 16 bits o dos registros de 32 bits, realizar una adición con una mantisa de 64 bits no es más lenta que usar una mantisa de 53 bits, por lo que la matemática de precisión extendida ofrece un cálculo más rápido sin inconvenientes en un lenguaje que admite un tipo adecuado para mantener los resultados temporales. No hay ninguna razón para culpar a Intel por proporcionar una FPU que podría realizar cálculos de punto flotante de la manera que fue también, el método más eficiente en los chips que no son FPU.

+1

Correcto, pero creo que * podemos * culpar a Intel por no proporcionar una forma de realizar operaciones aritméticas básicas redondeadas correctamente (con dobles de 64 bits) * en absoluto *. Sí, puede cambiar la precisión de FPU a 53 bits en lugar de 64 bits, pero eso es torpe, lento, los riesgos interfieren con el código de la biblioteca que espera la precisión de 64 bits y ni siquiera resuelve el problema: elimina el doble redondeo en el dominio normal, no cambia el rango de exponente, por lo que aún deja la posibilidad de doble redondeo en subdesbordamiento. SSE (2) es una gran mejora en este sentido. –

+0

@MarkDickinson: si bien existen aplicaciones especializadas que requieren un comportamiento de coma flotante poco consistente con operaciones que involucran tipos más cortos, para la mayoría de las aplicaciones es mejor contar con soporte adecuado para una precisión extendida. Veo que SSE (2) y x87 sirven para diferentes propósitos, y me hubiera gustado que los idiomas fueran compatibles con ellos, tanto los de promoción entusiasta como los estrictos de coma flotante; Además, las expresiones que implican tipos estrictos deberían en mi humilde opinión ser solo convertibles a tipos más grandes después de coaccionarlos "visiblemente" a su propio tipo, entonces si f1 y f2 fueran tipos de flotación estrictos, 'd1 = f1 * f2' ... – supercat

+0

... debe escribirse como 'd1 = (float) (f1 * f2);' [not 'd1 = (double) (f1 * f2);'!]. Supongo que en los casos en que alguien escribe 'd1 = f1 * f2;' hay una probabilidad muy alta de que (1) el código haya tenido la intención de decir 'd1 = (doble) f1 * f2;', (2) un programador que ve el código cree que significa eso, o (3) un programador que ve el código piensa que fue intencionado para decir eso. Exigir que el código se escriba como 'd1 = (float) (f1 * f2);' en los casos en que se pretende que el comportamiento elimine esos peligros. – supercat

0

Doble precisión es 11 bits menos que f80 (alrededor de 2.5 nibbles/dígitos), para muchas aplicaciones (principalmente juegos) no estaría de más. Pero necesitará toda la precisión disponible para, por ejemplo, el programa espacial o la aplicación médica.

Es un poco engañoso cuando algunos dicen que f80 (y desalentados por él) opera en la pila. Registros FPU y operaciones similares a la operación de pila, tal vez eso es lo que hace que la gente se confunda. En realidad, se basa en la memoria (carga/almacenamiento), no en la pila per se, en comparación con, por ejemplo, una convención de llamadas como cdecl stdcall, que en realidad pasa parámetros a través de la pila. y nada de malo en eso.

La gran ventaja de SSE en realidad es la operación de serialización, 2, 4, 8 valores a la vez, con muchas operaciones varian.Sí, puede transferir directamente para registrarse, pero transferirá esos valores a la memoria de todos modos al final.

La gran desventaja de f80 es que, por su longitud impar de 10 bytes, altera la alineación. tendrías que alinearlos 16 para un acceso más rápido. pero no es realmente practicable para array.

Aún tiene que usar fpu para operaciones trigonométricas y otras operaciones matemáticas trancedentales. Para asm, hay muchos trucos f80 realmente divertidos y útiles.

Para juegos y una aplicación sencilla regular (casi todos), puede usar el doble sin que muera alguien. Pero para algunas aplicaciones serias, matemáticas o científicas, simplemente no puedes abandonar f80.

+1

'serialize operation'. ¿Quieres decir? operación en paralelo ". O operación SIMD –

+2

' Todavía tiene que usar fpu para operaciones trigonométricas y otras operaciones matemáticas trancenales. Si se refiere a x86 FSIN, [FYL2X] (http://www.felixcloutier.com/x86/FYL2X.html) (log2), etc. luego no, eso es incorrecto. Las bibliotecas matemáticas implementan esas funciones en software, con matemáticas SSE. –

+1

Incluso antes de que x87 fuera obsoleto, las bibliotecas de matemáticas buenas no usaban FSIN, porque el valor interno de Pi utilizado para la reducción de rango no es lo suficientemente preciso; solo 66 bits Intel no puede cambiar esto, por razones de compatibilidad hacia atrás, pero [FSIN tiene errores grandes cerca de +/- pi/2] (https://randomascii.wordpress.com/2014/10/09/intel-underestimates-error -bounds-por-1-3-quintillion /) –

Cuestiones relacionadas