Isso é apenas para satisfazer minha própria curiosidade.
Existe uma implementação disso:
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;
}
em Rust? Se existir, publique o código.
Eu tentei e falhei. Não sei como codificar o número da bóia usando o formato inteiro. Aqui está a minha tentativa:
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
}
Referência:
1. Origem do Rápido InvSqrt do Quake3 () - Página 1
2. Entendendo a Raiz Quadrada Inversa Rápida do Quake3
3. RÁPIDO QUADRADO INVERSO RÁPIDO.pdf
4. código fonte: q_math.c # L552-L572
union
.union
funciona.memcpy
definitivamente funciona, embora seja detalhado.rsqrtss
ersqrtps
, introduzidas no Pentium III em 1999, são mais rápidas e precisas que esse código. BRAÇO NEON tem ovrsqrte
que é semelhante. E qualquer que seja o cálculo usado pelo Quake III, provavelmente seria feito na GPU hoje em dia.Respostas:
Existe uma função para isso:
f32::to_bits
que retorna umu32
. Há também a função para a outra direção:f32::from_bits
que aceita umu32
argumento as. Essas funções são preferidas,mem::transmute
pois a última éunsafe
difícil de usar.Com isso, aqui está a implementação de
InvSqrt
:( Parque infantil )
Essa função é compilada no seguinte assembly em x86-64:
Não encontrei nenhum conjunto de referência (se tiver, por favor me diga!), Mas me parece bastante bom. Só não sei por que o flutuador foi movido
eax
apenas para fazer a subtração de deslocamento e número inteiro. Talvez os registros SSE não suportem essas operações?O clang 9.0 with
-O3
compila o código C basicamente para o mesmo assembly . Então esse é um bom sinal.Vale ressaltar que, se você realmente deseja usar isso na prática: por favor, não. Como o benrg apontou nos comentários , as modernas CPUs x86 têm uma instrução especializada para essa função, que é mais rápida e precisa do que esse hack. Infelizmente,
1.0 / x.sqrt()
parece não otimizar essa instrução . Portanto, se você realmente precisa da velocidade, usar os_mm_rsqrt_ps
intrínsecos provavelmente é o caminho a percorrer. Isso, no entanto, exige novamenteunsafe
código. Não vou entrar em muitos detalhes nesta resposta, pois uma minoria de programadores realmente precisará dela.fonte
addss
oumulss
. Mas se os outros 96 bits de xmm0 puderem ser ignorados, pode-se usar apsrld
instrução. O mesmo vale para subtração de número inteiro.fast_inv_sqrt
é apenas uma etapa da iteração de Newton-Raphson para encontrar uma melhor aproximação deinv_sqrt
. Não há nada de inseguro nessa parte. O truque está na primeira parte, que encontra uma boa aproximação. Isso funciona porque ele está fazendo uma divisão inteira por 2 da parte expoente do flutuador, e de fatosqrt(pow(0.5,x))=pow(0.5,x/2)
movd
para EAX e vice-versa é uma otimização perdida pelos compiladores atuais. (E sim, as convenções de chamada passam por escalar / retornar escalarfloat
no elemento baixo de um XMM e permitem que os bits altos sejam lixo. Mas observe que, se ele foi estendido para zero, pode facilmente ficar assim: a mudança à direita não introduz não- zero elementos e nem subtração_mm_set_epi32(0,0,0,0x5f3759df)
, ou seja, umamovd
carga Você iria precisar de um.movdqa xmm1,xmm0
para copiar o reg antespsrld
Bypass latência encaminhe instrução FP para inteiro e vice-versa está oculta por.mulss
latência.Este é implementado com menos conhecido
union
em Rust:Fiz alguns micro benchmarks usando
criterion
engradado em uma caixa Linux x86-64. Surpreendentemente, o próprio Rustsqrt().recip()
é o mais rápido. Mas é claro que qualquer resultado de micro benchmark deve ser obtido com um grão de sal.fonte
sqrt().inv()
é o mais rápido. Atualmente, sqrt e inv são instruções únicas e são bastante rápidas. Doom foi escrito nos dias em que não era seguro supor que houvesse ponto flutuante de hardware, e funções transcendentais como o sqrt definitivamente seriam software. +1 para os benchmarks.transmute
aparentemente é diferenteto_
efrom_bits
- eu esperaria que eles fossem equivalentes a instruções antes mesmo da otimização.Você pode usar
std::mem::transmute
para fazer a conversão necessária:Você pode procurar um exemplo ao vivo aqui: aqui
fonte
f32::to_bits
ef32::from_bits
. Ele também carrega a intenção claramente diferente de transmutar, que a maioria das pessoas provavelmente considera "mágica".unsafe
deve ser evitado aqui, pois não é necessário.