John Carmack tiene una función especial en el código fuente de Quake III que calcula la raíz cuadrada inversa de un flotador, 4 veces más rápido que lo normal (float)(1.0/sqrt(x))
, incluyendo una 0x5f3759df
constante extraña . Vea el código a continuación. ¿Alguien puede explicar línea por línea qué está sucediendo exactamente aquí y por qué funciona mucho más rápido que la implementación normal?
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y;
i = 0x5f3759df - ( i >> 1 );
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) );
#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) );
#endif
#endif
return y;
}
Respuestas:
FYI. Carmack no lo escribió. Terje Mathisen y Gary Tarolli se atribuyen un mérito parcial (y muy modesto) por ello, además de acreditar algunas otras fuentes.
Cómo se derivó la constante mítica es algo misterioso.
Para citar a Gary Tarolli:
Una constante ligeramente mejor, desarrollada por un matemático experto (Chris Lomont) que intenta averiguar cómo funcionaba el algoritmo original es:
A pesar de esto, su intento inicial de una versión matemáticamente 'superior' de la raíz cuadrada de id (que llegó a casi la misma constante) resultó ser inferior a la desarrollada inicialmente por Gary a pesar de ser matemáticamente mucho más 'pura'. No podía explicar por qué id's era tan excelente iirc.
fuente
Por supuesto, en estos días, resulta ser mucho más lento que simplemente usar un sqrt de FPU (especialmente en 360 / PS3), porque el intercambio entre los registros float e int induce un load-hit-store, mientras que la unidad de punto flotante puede hacer un cuadrado recíproco root en hardware.
Simplemente muestra cómo las optimizaciones tienen que evolucionar a medida que cambia la naturaleza del hardware subyacente.
fuente
Greg Hewgill e IllidanS4 dieron un vínculo con una excelente explicación matemática. Intentaré resumirlo aquí para aquellos que no quieran entrar demasiado en detalles.
Cualquier función matemática, con algunas excepciones, se puede representar mediante una suma polinomial:
se puede transformar exactamente en:
Donde a0, a1, a2, ... son constantes . El problema es que para muchas funciones, como raíz cuadrada, para el valor exacto, esta suma tiene un número infinito de miembros, no termina en algún x ^ n . Pero, si nos detenemos en algún x ^ n todavía tendríamos un resultado con cierta precisión.
Entonces, si tenemos:
En este caso particular, decidieron descartar todos los miembros polinomiales por encima del segundo, probablemente debido a la velocidad de cálculo:
Y ahora ha bajado la tarea de calcular a0 y a1 para que y tenga la menor diferencia con el valor exacto. Han calculado que los valores más adecuados son:
Entonces, cuando pones esto en la ecuación, obtienes:
Que es lo mismo que la línea que ves en el código:
Editar: en realidad, aquí
y = 0x5f375a86 - 0.5*x
no es lo mismo quei = 0x5f375a86 - (i >> 1);
ya que cambiar el flotador como entero no solo divide por dos sino que también divide el exponente por dos y causa algunos otros artefactos, pero aún así se reduce a calcular algunos coeficientes a0, a1, a2 ...En este punto, han descubierto que la precisión de este resultado no es suficiente para el propósito. Así que, además, hicieron solo un paso de la iteración de Newton para mejorar la precisión del resultado:
Podrían haber hecho algunas iteraciones más en un ciclo, cada una mejorando el resultado, hasta que se alcance la precisión requerida. ¡Así es exactamente como funciona en CPU / FPU! Pero parece que solo una iteración fue suficiente, lo que también fue una bendición para la velocidad. CPU / FPU hace tantas iteraciones como sea necesario para alcanzar la precisión del número de punto flotante en el que se almacena el resultado y tiene un algoritmo más general que funciona para todos los casos.
Entonces, en resumen, lo que hicieron fue:
Use (casi) el mismo algoritmo que CPU / FPU, aproveche la mejora de las condiciones iniciales para el caso especial de 1 / sqrt (x) y no calcule todo el camino hasta la precisión a la que llegará la CPU / FPU, pero se detendrá antes, por lo tanto ganando velocidad de cálculo.
fuente
Según este bonito artículo escrito hace un tiempo ...
Es una lectura realmente buena. Esto es solo una pequeña parte.
fuente
Tenía curiosidad por ver cuál era la constante como flotante, así que simplemente escribí este fragmento de código y busqué en Google el número entero que apareció.
Parece que la constante es "Una aproximación entera a la raíz cuadrada de 2 ^ 127 mejor conocida por la forma hexadecimal de su representación de punto flotante, 0x5f3759df" https://mrob.com/pub/math/numbers-18.html
En el mismo sitio lo explica todo. https://mrob.com/pub/math/numbers-16.html#le009_16
fuente