C'est juste pour satisfaire ma propre curiosité.
Y a-t-il une implémentation de ceci:
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;
}
à Rust? S'il existe, affichez le code.
J'ai essayé et j'ai échoué. Je ne sais pas comment encoder le nombre flottant en utilisant le format entier. Voici ma tentative:
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
}
Référence:
1. Origine de Quake3's Fast InvSqrt () - Page 1
2. Comprendre la racine carrée inverse rapide de Quake
3. FAST INVERSE SQUARE ROOT.pdf
4. code source: q_math.c # L552-L572
union
.union
marche non plus.memcpy
fonctionne certainement, bien qu'il soit verbeux.rsqrtss
etrsqrtps
, introduites avec le Pentium III en 1999, sont plus rapides et plus précises que ce code. ARM NEON avrsqrte
qui est similaire. Et quels que soient les calculs utilisés par Quake III, cela serait probablement fait sur le GPU ces jours-ci de toute façon.Réponses:
Il y a une fonction pour cela:
f32::to_bits
qui retourne unu32
. Il y a aussi la fonction pour l'autre direction:f32::from_bits
qui prend unu32
argument comme. Ces fonctions sont préférées par rapportmem::transmute
à cette dernièreunsafe
et délicate à utiliser.Avec cela, voici la mise en œuvre de
InvSqrt
:( Aire de jeux )
Cette fonction se compile vers l'assembly suivant sur x86-64:
Je n'ai trouvé aucun assemblage de référence (si vous en avez, dites-le moi!), Mais il me semble assez bon. Je ne sais tout simplement pas pourquoi le flotteur a été déplacé
eax
juste pour effectuer le décalage et la soustraction d'entiers. Peut-être que les registres SSE ne prennent pas en charge ces opérations?clang 9.0 avec
-O3
compile le code C en gros dans le même assemblage . C'est donc un bon signe.Il convient de souligner que si vous souhaitez réellement l'utiliser dans la pratique: veuillez ne pas le faire. Comme l'a souligné benrg dans les commentaires , les processeurs x86 modernes ont une instruction spécialisée pour cette fonction qui est plus rapide et plus précise que ce hack. Malheureusement,
1.0 / x.sqrt()
ne semble pas optimiser cette instruction . Donc, si vous avez vraiment besoin de vitesse, l'utilisation de l'_mm_rsqrt_ps
intrinsèque est probablement la voie à suivre. Cependant, cela nécessite à nouveau duunsafe
code. Je n'entrerai pas dans les détails de cette réponse, car une minorité de programmeurs en aura réellement besoin.la source
addss
oumulss
. Mais si les 96 autres bits de xmm0 peuvent être ignorés, alors on pourrait utiliser l'psrld
instruction. Il en va de même pour la soustraction entière.fast_inv_sqrt
n'est qu'une étape d'itération de Newton-Raphson pour trouver une meilleure approximation deinv_sqrt
. Il n'y a rien de dangereux dans cette partie. La ruse est dans la première partie, qui trouve une bonne approximation. Cela fonctionne parce qu'il fait une division entière par 2 sur la partie exposante du flotteur, et en effetsqrt(pow(0.5,x))=pow(0.5,x/2)
movd
à EAX et retour est une optimisation manquée par les compilateurs actuels. (Et oui, les conventions d'appel passent / retournent un scalairefloat
dans l'élément bas d'un XMM et permettent aux bits élevés d'être des ordures. Mais notez que s'il était étendu à zéro, il peut facilement rester ainsi: le décalage à droite n'introduit zéro éléments et ni ne soustraction_mm_set_epi32(0,0,0,0x5f3759df)
, soit unemovd
charge Vous auriez besoin d' un.movdqa xmm1,xmm0
copier le reg avantpsrld
Bypass temps d' attente de transfert d'instruction FP à l' entier et vice versa est caché par.mulss
temps d' attente.Celui-ci est implémenté avec moins connu
union
dans Rust:A fait quelques micro benchmarks en utilisant une
criterion
caisse sur une boîte Linux x86-64. Étonnamment Rust'ssqrt().recip()
est le plus rapide. Mais bien sûr, tout résultat de micro-benchmark doit être pris avec un grain de sel.la source
sqrt().inv()
c'est le plus rapide. Sqrt et inv sont des instructions uniques de nos jours, et vont assez vite. Doom a été écrit à l'époque où il n'était pas sûr de supposer qu'il y avait du matériel flottant du tout, et les fonctions transcendantales comme sqrt auraient certainement été des logiciels. +1 pour les benchmarks.transmute
est apparemment différent deto_
etfrom_bits
- je m'attends à ce que ceux-ci soient équivalents à l'instruction avant même l'optimisation.Vous pouvez utiliser
std::mem::transmute
pour effectuer la conversion nécessaire:Vous pouvez rechercher un exemple en direct ici: ici
la source
f32::to_bits
etf32::from_bits
. Il porte également clairement l'intention contrairement à la transmutation, que la plupart des gens considèrent probablement comme «magique».unsafe
devrait être évité ici, car ce n'est pas nécessaire.