¿Qué hace realmente las matemáticas rápidas de gcc?

144

Entiendo que la --ffast-mathbandera de gcc puede aumentar en gran medida la velocidad de las operaciones de flotación, y se sale de los estándares IEEE, pero parece que no puedo encontrar información sobre lo que realmente sucede cuando está encendido. ¿Alguien puede explicar algunos de los detalles y tal vez dar un ejemplo claro de cómo algo cambiaría si la bandera estuviera encendida o apagada?

Intenté buscar en SO para preguntas similares, pero no pude encontrar nada que explicara el funcionamiento de las matemáticas rápidas.

Ponml
fuente

Respuestas:

86

Como mencionó, permite optimizaciones que no preservan el estricto cumplimiento de IEEE.

Un ejemplo es este:

x = x*x*x*x*x*x*x*x;

a

x *= x;
x *= x;
x *= x;

Debido a que la aritmética de punto flotante no es asociativa, el orden y la factorización de las operaciones afectarán los resultados debido al redondeo. Por lo tanto, esta optimización no se realiza bajo un comportamiento estricto de FP.

Realmente no he verificado si GCC realmente hace esta optimización en particular. Pero la idea es la misma.

Místico
fuente
25
@Andrey: Para este ejemplo, pasas de 7 multiplicados a 3.
Mysticial
44
@Andrey: Matemáticamente, será correcto. Pero el resultado puede diferir ligeramente en los últimos bits debido al redondeo diferente.
Mysticial
1
En la mayoría de los casos, esta ligera diferencia no importará (relativamente en el orden de 10 ^ -16 double, pero varía según la aplicación). Una cosa a tener en cuenta es que las optimizaciones de matemáticas rápidas no necesariamente agregan "más" redondeo. La única razón por la que no cumple con IEEE es porque la respuesta es diferente (aunque ligeramente) de lo que está escrito.
Mysticial
1
@usuario: la magnitud del error depende de los datos de entrada. Debe ser pequeño en relación con el resultado. Por ejemplo, si xes menor que 10, el error en el ejemplo de Mystical disminuirá alrededor de 10 ^ -10. Pero si x = 10e20, es probable que el error sea de muchos millones.
Ben Voigt
3
@stefanct es realmente acerca de -fassociative-mathlo que está incluido en -funsafe-math-optimizationsla cual a su vez está habilitado con -ffast-math ¿Por qué no optimizar el CCG a*a*a*a*a*aa (a*a*a)*(a*a*a)?
phuclv
256

-ffast-math hace mucho más que simplemente romper el estricto cumplimiento de IEEE.

En primer lugar, por supuesto, rompe el estricto cumplimiento de IEEE, lo que permite, por ejemplo, reordenar las instrucciones a algo que sea matemáticamente igual (idealmente) pero no exactamente igual en coma flotante.

En segundo lugar, deshabilita la configuración errnodespués de las funciones matemáticas de una sola instrucción, lo que significa evitar escribir en una variable local de hilo (esto puede hacer una diferencia del 100% para esas funciones en algunas arquitecturas).

En tercer lugar, supone que todas las matemáticas son finitas , lo que significa que no se realizan comprobaciones de NaN (o cero) en el lugar donde tendrían efectos perjudiciales. Simplemente se supone que esto no va a suceder.

Cuarto, permite aproximaciones recíprocas para la división y la raíz cuadrada recíproca.

Además, deshabilita el cero con signo (el código supone que el cero con signo no existe, incluso si el objetivo lo admite) y el redondeo matemático, lo que permite, entre otras cosas, el plegado constante en tiempo de compilación.

Por último, se genera un código que asume que no hay interrupciones de hardware pueden ocurrir debido a la señalización / atrapando matemáticas (es decir, si estos no se puede desactivar en la arquitectura de destino y por lo tanto no suceda , no van a ser manejados).

Damon
fuente
15
Damon, gracias! ¿Puedes agregar algunas referencias? Como gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html " -ffast-math Conjuntos -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-señalización -nans y -fcx-limited-range. Esta opción hace que se defina la macro del preprocesador FAST_MATH . "y algo de glibc, como ( math.hnear math_errhandling)" Por defecto, todas las funciones admiten tanto el manejo de errores como el de excepción. En el modo matemático rápido de gcc y si las funciones en línea están definidas, esto podría no ser cierto. "
osgx
44
@javapowered: si es "peligroso" depende de las garantías que necesite. -ffast-mathpermite al compilador cortar algunas esquinas y romper algunas promesas (como se explicó), que en general no es peligroso como tal y no es un problema para la mayoría de las personas. Para la mayoría de las personas, es lo mismo, solo que más rápido. Sin embargo, si su código asume y se basa en estas promesas, entonces su código puede comportarse de manera diferente de lo que espera. Por lo general, esto significa que el programa parecerá funcionar bien, en su mayoría, pero algunos resultados pueden ser "inesperados" (por ejemplo, en una simulación de física, dos objetos podrían no chocar correctamente).
Damon
2
@Royi: Los dos deberían ser independientes el uno del otro. -O2generalmente permite "todas" las optimizaciones legales, excepto aquellas que cambian el tamaño por velocidad. -O3También permite optimizaciones que cambian el tamaño por la velocidad. Todavía mantiene el 100% de corrección. -ffast-mathintenta hacer que las operaciones matemáticas sean más rápidas permitiendo un comportamiento "ligeramente incorrecto" que generalmente no es dañino, pero se consideraría incorrecto por la redacción de la norma. Si su código es realmente muy diferente en velocidad en dos compiladores (no solo 1-2%), verifique que su código cumpla estrictamente con los estándares y ...
Damon
1
... produce cero advertencias. Además, asegúrese de no obstaculizar las reglas de alias y cosas como la auto-vectorización. En principio, GCC debería funcionar al menos tan bien (generalmente mejor en mi experiencia) como MSVC. Cuando ese no sea el caso, probablemente haya cometido un sutil error que MSVC simplemente ignora pero que hace que GCC desactive una optimización. Debería dar ambas opciones si quiere las dos, sí.
Damon
1
@Royi: Ese código no me parece realmente pequeño y simple, no es algo que uno pueda analizar en profundidad en unos minutos (o incluso horas). Entre otras cosas, implica un aspecto aparentemente inofensivo #pragma omp parallel for, y dentro del cuerpo del bucle está leyendo y escribiendo en direcciones señaladas por argumentos de función, y realiza una cantidad no trivial de ramificación. Como una suposición sin educación, es posible que esté intercambiando cachés desde su invocación de subprocesos definida por la implementación, y MSVC puede evitar incorrectamente los almacenes intermedios que las reglas de alias obligarían. Imposible decirlo.
Damon