John Carmack a une fonction spéciale dans le code source de Quake III qui calcule la racine carrée inverse d'un float, 4x plus rapide que normal (float)(1.0/sqrt(x))
, y compris une 0x5f3759df
constante étrange . Voir le code ci-dessous. Quelqu'un peut-il expliquer ligne par ligne ce qui se passe exactement ici et pourquoi cela fonctionne beaucoup plus rapidement que la mise en œuvre régulière?
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;
}
Réponses:
FYI. Carmack ne l'a pas écrit. Terje Mathisen et Gary Tarolli en ont tous les deux un crédit partiel (et très modeste), ainsi que d'autres sources.
Comment la constante mythique a été dérivée est quelque chose d'un mystère.
Pour citer Gary Tarolli:
Une constante légèrement meilleure, développée par un mathématicien expert (Chris Lomont) essayant de comprendre le fonctionnement de l'algorithme d'origine est:
Malgré cela, sa tentative initiale d'une version mathématiquement «supérieure» du sqrt d'id (qui atteignait presque la même constante) s'est avérée inférieure à celle initialement développée par Gary bien qu'elle soit mathématiquement beaucoup plus «pure». Il ne pouvait pas expliquer pourquoi les id étaient si excellents.
la source
Bien sûr, ces jours-ci, cela s'avère beaucoup plus lent que d'utiliser simplement le sqrt d'un FPU (en particulier sur 360 / PS3), car le basculement entre les registres float et int induit un load-hit-store, tandis que l'unité à virgule flottante peut faire un carré réciproque root dans le matériel.
Il montre simplement comment les optimisations doivent évoluer en fonction de la nature des changements matériels sous-jacents.
la source
Greg Hewgill et IllidanS4 ont donné un lien avec une excellente explication mathématique. Je vais essayer de résumer ici pour ceux qui ne veulent pas trop entrer dans les détails.
Toute fonction mathématique, à quelques exceptions près, peut être représentée par une somme polynomiale:
peut être exactement transformé en:
Où a0, a1, a2, ... sont des constantes . Le problème est que pour de nombreuses fonctions, comme la racine carrée, pour une valeur exacte cette somme a un nombre infini de membres, elle ne se termine pas à un certain x ^ n . Mais, si nous nous arrêtons à un x ^ n, nous aurions toujours un résultat avec une certaine précision.
Donc, si nous avons:
Dans ce cas particulier, ils ont décidé de rejeter tous les membres polynomiaux au-dessus de la seconde, probablement à cause de la vitesse de calcul:
Et la tâche consiste maintenant à calculer a0 et a1 afin que y ait le moins de différence avec la valeur exacte. Ils ont calculé que les valeurs les plus appropriées sont:
Donc, lorsque vous mettez cela dans l'équation, vous obtenez:
Qui est la même que la ligne que vous voyez dans le code:
Edit: en fait, ce
y = 0x5f375a86 - 0.5*x
n'est pas la même chose quei = 0x5f375a86 - (i >> 1);
puisque le décalage de float comme entier non seulement divise par deux, mais divise également l'exposant par deux et provoque d'autres artefacts, mais cela revient toujours à calculer certains coefficients a0, a1, a2 ....À ce stade, ils ont découvert que la précision de ce résultat n'est pas suffisante à cet effet. Ainsi, ils n'ont en outre fait qu'une seule étape de l'itération de Newton pour améliorer la précision du résultat:
Ils auraient pu faire d'autres itérations en boucle, chacune améliorant le résultat, jusqu'à ce que la précision requise soit atteinte. C'est exactement comme cela que cela fonctionne dans CPU / FPU! Mais il semble qu'une seule itération ait suffi, ce qui était également une bénédiction pour la vitesse. CPU / FPU fait autant d'itérations que nécessaire pour atteindre la précision du nombre à virgule flottante dans lequel le résultat est stocké et il a un algorithme plus général qui fonctionne dans tous les cas.
Donc, en bref, ce qu'ils ont fait est:
Utilisez (presque) le même algorithme que CPU / FPU, exploitez l'amélioration des conditions initiales pour le cas particulier de 1 / sqrt (x) et ne calculez pas jusqu'à la précision CPU / FPU ira mais arrêtez plus tôt, donc gagner en vitesse de calcul.
la source
D' après ce bel article écrit il y a quelque temps ...
C'est une très bonne lecture. Ce n'est qu'un tout petit morceau de celui-ci.
la source
J'étais curieux de voir ce qu'était la constante en tant que flottant, alors j'ai simplement écrit ce morceau de code et googlé l'entier qui apparaissait.
Il semble que la constante soit "Une approximation entière de la racine carrée de 2 ^ 127 mieux connue sous la forme hexadécimale de sa représentation en virgule flottante, 0x5f3759df" https://mrob.com/pub/math/numbers-18.html
Sur le même site, il explique tout. https://mrob.com/pub/math/numbers-16.html#le009_16
la source