Esto es solo para satisfacer mi propia curiosidad.
¿Hay una implementación de esto?
float InvSqrt (float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x;
i = 0x5f3759df - (i>>1);
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x);
return x;
}
en el óxido? Si existe, publique el código.
Lo intenté y fallé. No sé cómo codificar el número flotante con formato entero. Aquí está mi intento:
fn main() {
println!("Hello, world!");
println!("sqrt1: {}, ",sqrt2(100f64));
}
fn sqrt1(x: f64) -> f64 {
x.sqrt()
}
fn sqrt2(x: f64) -> f64 {
let mut x = x;
let xhalf = 0.5*x;
let mut i = x as i64;
println!("sqrt1: {}, ", i);
i = 0x5f375a86 as i64 - (i>>1);
x = i as f64;
x = x*(1.5f64 - xhalf*x*x);
1.0/x
}
Referencia:
1. Origen de Quake3's Fast InvSqrt () - Página 1
2. Comprensión de Quake's Fast Inverse Square Root
3. FAST INVERSE SQUARE ROOT.pdf
4. código fuente: q_math.c # L552-L572

union.unionfuncione.memcpydefinitivamente funciona, aunque es detallado.rsqrtssyrsqrtps, introducidas con el Pentium III en 1999, son más rápidas y precisas que este código. BRAZO NEON tienevrsqrteque es similar. Y cualesquiera que sean los cálculos que utilizó Quake III para esto, probablemente se realizarían en la GPU en estos días de todos modos.Respuestas:
Hay una función para eso:
f32::to_bitsque devuelve unu32. También existe la función para la otra dirección:f32::from_bitsque toma unu32argumento. Estas funciones son preferibles amem::transmutelas últimas,unsafey es difícil de usar.Con eso, aquí está la implementación de
InvSqrt:( Área de juegos )
Esta función se compila en el siguiente ensamblado en x86-64:
No he encontrado ningún ensamblaje de referencia (si lo ha hecho, ¡dígamelo!), Pero me parece bastante bueno. No estoy seguro de por qué se movió el flotador
eaxsolo para hacer el cambio y la resta de enteros. ¿Quizás los registros SSE no admiten esas operaciones?clang 9.0 con
-O3compila el código C para básicamente el mismo ensamblado . Entonces esa es una buena señal.Vale la pena señalar que si realmente quieres usar esto en la práctica: por favor no lo hagas. Como señaló benrg en los comentarios , las CPU modernas x86 tienen una instrucción especializada para esta función que es más rápida y precisa que este truco. Desafortunadamente,
1.0 / x.sqrt()no parece optimizar a esa instrucción . Entonces, si realmente necesita la velocidad, usar los_mm_rsqrt_psintrínsecos es probablemente el camino a seguir. Sin embargo, esto nuevamente requiereunsafecódigo. No entraré en muchos detalles en esta respuesta, ya que una minoría de programadores realmente lo necesitará.fuente
addssomulss. Pero si los otros 96 bits de xmm0 pueden ignorarse, entonces uno podría usar lapsrldinstrucción. Lo mismo ocurre con la resta de enteros.fast_inv_sqrtes solo un paso de iteración de Newton-Raphson para encontrar una mejor aproximación deinv_sqrt. No hay nada inseguro en esa parte. El truco está en la primera parte, que encuentra una buena aproximación. Eso funciona porque está haciendo un número entero dividido por 2 en la parte exponente del flotador y, de hechosqrt(pow(0.5,x))=pow(0.5,x/2)movdEAX y viceversa es una optimización perdida por los compiladores actuales. (Y sí, pasan convenciones de llamada / retorno escalarfloaten el elemento de baja de un XMM y permiten altos bits a la basura Pero tenga en cuenta que si. Fue extendida a cero, puede mantenerse fácilmente de esa manera: desplazamiento de la derecha no introduce no cero elementos y tampoco resta de_mm_set_epi32(0,0,0,0x5f3759df), es decir, unamovdcarga. Necesitaría unmovdqa xmm1,xmm0para copiar el registro antespsrld. Evitar la latencia del reenvío de instrucciones FP a entero y viceversa está oculto por lamulsslatencia.Este se implementa con menos conocido
unionen Rust:Hice algunos micro benchmarks usando
criterioncajón en una caja Linux x86-64. Sorprendentemente, el propio Rustsqrt().recip()es el más rápido. Pero, por supuesto, cualquier resultado micro referencial debe tomarse con un grano de sal.fuente
sqrt().inv()es el más rápido. Tanto sqrt como inv son instrucciones únicas en estos días, y van bastante rápido. Doom se escribió en los días en que no era seguro asumir que había un punto flotante de hardware, y que las funciones trascendentales como sqrt definitivamente habrían sido software. +1 para los puntos de referencia.transmuteaparentemente es diferenteto_yfrom_bitsespero que sean equivalentes a las instrucciones incluso antes de la optimización.Puede usar
std::mem::transmutepara hacer la conversión necesaria:Puedes buscar un ejemplo en vivo aquí: aquí
fuente
f32::to_bitsyf32::from_bits. También lleva la intención claramente diferente a transmutar, que la mayoría de la gente probablemente ve como "magia".unsafedebería evitarse aquí, ya que no es necesario.