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
.union
funcione.memcpy
definitivamente funciona, aunque es detallado.rsqrtss
yrsqrtps
, introducidas con el Pentium III en 1999, son más rápidas y precisas que este código. BRAZO NEON tienevrsqrte
que 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_bits
que devuelve unu32
. También existe la función para la otra dirección:f32::from_bits
que toma unu32
argumento. Estas funciones son preferibles amem::transmute
las últimas,unsafe
y 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
eax
solo para hacer el cambio y la resta de enteros. ¿Quizás los registros SSE no admiten esas operaciones?clang 9.0 con
-O3
compila 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_ps
intrínsecos es probablemente el camino a seguir. Sin embargo, esto nuevamente requiereunsafe
código. No entraré en muchos detalles en esta respuesta, ya que una minoría de programadores realmente lo necesitará.fuente
addss
omulss
. Pero si los otros 96 bits de xmm0 pueden ignorarse, entonces uno podría usar lapsrld
instrucción. Lo mismo ocurre con la resta de enteros.fast_inv_sqrt
es 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)
movd
EAX y viceversa es una optimización perdida por los compiladores actuales. (Y sí, pasan convenciones de llamada / retorno escalarfloat
en 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, unamovd
carga. Necesitaría unmovdqa xmm1,xmm0
para copiar el registro antespsrld
. Evitar la latencia del reenvío de instrucciones FP a entero y viceversa está oculto por lamulss
latencia.Este se implementa con menos conocido
union
en Rust:Hice algunos micro benchmarks usando
criterion
cajó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.transmute
aparentemente es diferenteto_
yfrom_bits
espero que sean equivalentes a las instrucciones incluso antes de la optimización.Puede usar
std::mem::transmute
para hacer la conversión necesaria:Puedes buscar un ejemplo en vivo aquí: aquí
fuente
f32::to_bits
yf32::from_bits
. También lleva la intención claramente diferente a transmutar, que la mayoría de la gente probablemente ve como "magia".unsafe
debería evitarse aquí, ya que no es necesario.