John Carmack tem uma função especial no código-fonte do Quake III que calcula a raiz quadrada inversa de um float, 4x mais rápido que o normal (float)(1.0/sqrt(x))
, incluindo uma 0x5f3759df
constante estranha . Veja o código abaixo. Alguém pode explicar linha por linha o que exatamente está acontecendo aqui e por que isso funciona muito mais rápido do que a implementação 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;
}
Respostas:
PARA SUA INFORMAÇÃO. Carmack não o escreveu. Terje Mathisen e Gary Tarolli têm crédito parcial (e muito modesto) por ele, bem como algumas outras fontes.
Como a constante mítica foi derivada é um mistério.
Para citar Gary Tarolli:
Uma constante ligeiramente melhor, desenvolvida por um matemático especialista (Chris Lomont) tentando descobrir como o algoritmo original funcionava, é:
Apesar disso, sua tentativa inicial de uma versão matematicamente 'superior' do sqrt do id (que chegou a quase a mesma constante) provou ser inferior à inicialmente desenvolvida por Gary, apesar de ser matematicamente muito mais 'puro'. Ele não conseguia explicar por que o id era tão excelente iirc.
fonte
É claro que hoje em dia, acaba sendo muito mais lento do que apenas usar um sqrt de FPU (especialmente no 360 / PS3), porque a troca entre os registradores float e int induz um load-hit-store, enquanto a unidade de ponto flutuante pode fazer um quadrado recíproco root no hardware.
Isso apenas mostra como as otimizações precisam evoluir conforme a natureza das mudanças de hardware subjacentes.
fonte
Greg Hewgill e IllidanS4 deram um link com uma excelente explicação matemática. Vou tentar resumir aqui para aqueles que não querem entrar muito em detalhes.
Qualquer função matemática, com algumas exceções, pode ser representada por uma soma polinomial:
pode ser exatamente transformado em:
Onde a0, a1, a2, ... são constantes . O problema é que para muitas funções, como raiz quadrada, para valor exato essa soma tem um número infinito de membros, ela não termina em algum x ^ n . Mas, se pararmos em algum x ^ n , ainda teremos um resultado com alguma precisão.
Então, se tivermos:
Neste caso particular, eles decidiram descartar todos os membros polinomiais acima do segundo, provavelmente por causa da velocidade de cálculo:
E agora chegou a tarefa de calcular a0 e a1 para que y tenha a menor diferença do valor exato. Eles calcularam que os valores mais apropriados são:
Então, quando você coloca isso na equação, você obtém:
Que é a mesma linha que você vê no código:
Edit: na verdade aqui
y = 0x5f375a86 - 0.5*x
não é o mesmo quei = 0x5f375a86 - (i >> 1);
mudar o float como inteiro não só divide por dois, mas também divide o expoente por dois e causa alguns outros artefatos, mas ainda se resume a calcular alguns coeficientes a0, a1, a2 ....Nesse ponto, eles descobriram que a precisão desse resultado não é suficiente para o propósito. Então, eles também fizeram apenas uma etapa da iteração de Newton para melhorar a precisão do resultado:
Eles poderiam ter feito mais algumas iterações em um loop, cada uma melhorando o resultado, até que a precisão necessária seja alcançada. É exatamente assim que funciona na CPU / FPU! Mas parece que apenas uma iteração foi suficiente, o que também foi uma bênção para a velocidade. A CPU / FPU faz quantas iterações forem necessárias para atingir a precisão do número de ponto flutuante no qual o resultado é armazenado e tem um algoritmo mais geral que funciona para todos os casos.
Resumindo, o que eles fizeram é:
Use (quase) o mesmo algoritmo que CPU / FPU, explore a melhoria das condições iniciais para o caso especial de 1 / sqrt (x) e não calcule todo o caminho para a precisão CPU / FPU irá para, mas pare antes, portanto ganhando em velocidade de cálculo.
fonte
De acordo com este belo artigo escrito há algum tempo ...
É uma leitura muito boa. Este é apenas um pequeno pedaço dela.
fonte
Eu estava curioso para ver o que era a constante como um float, então simplesmente escrevi este trecho de código e pesquisei o número inteiro que apareceu.
Parece que a constante é "Uma aproximação de inteiro da raiz quadrada de 2 ^ 127, mais conhecida pela forma hexadecimal de sua representação de ponto flutuante, 0x5f3759df" https://mrob.com/pub/math/numbers-18.html
No mesmo site, ele explica tudo. https://mrob.com/pub/math/numbers-16.html#le009_16
fonte