Dies ist nur, um meine eigene Neugier zu befriedigen.
Gibt es eine Implementierung davon:
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;
}
in Rust? Wenn es existiert, geben Sie den Code ein.
Ich habe es versucht und bin gescheitert. Ich weiß nicht, wie ich die Gleitkommazahl im Ganzzahlformat codieren soll. Hier ist mein Versuch:
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
}
Referenz:
1. Ursprung von Quake3s Fast InvSqrt () - Page 1
2. Grundlegendes zu Quakes Fast Inverse Square Root
3. FAST INVERSE SQUARE ROOT.pdf
4. Quellcode: q_math.c # L552-L572
union
.union
funktioniert.memcpy
funktioniert definitiv, obwohl es ausführlich ist.rsqrtss
undrsqrtps
Anweisungen, im Jahr 1999 mit dem Pentium III eingeführt, sind schneller und genauer als dieser Code. ARM NEON hatvrsqrte
was ähnlich ist. Und für welche Berechnungen Quake III dies auch verwendete, würde es heutzutage wahrscheinlich sowieso auf der GPU durchgeführt werden.Antworten:
Dafür gibt es eine Funktion:
f32::to_bits
die ein zurückgibtu32
. Es gibt auch die Funktion für die andere Richtung:f32::from_bits
die einu32
als Argument nimmt. Diese Funktionen werden gegenübermem::transmute
letzteren bevorzugtunsafe
und sind schwierig zu bedienen.Damit ist hier die Implementierung von
InvSqrt
:( Spielplatz )
Diese Funktion wird auf x86-64 in die folgende Assembly kompiliert:
Ich habe keine Referenzbaugruppe gefunden (wenn ja, bitte sagen Sie es mir!), Aber es scheint mir ziemlich gut zu sein. Ich bin mir nur nicht sicher, warum der Float nur bewegt wurde
eax
, um die Verschiebung und die Ganzzahlsubtraktion durchzuführen. Vielleicht unterstützen SSE-Register diese Operationen nicht?clang 9.0 mit
-O3
kompiliert den C-Code auf im Grunde dieselbe Assembly . Das ist also ein gutes Zeichen.Es sei darauf hingewiesen, dass Sie dies bitte nicht tun sollten, wenn Sie dies tatsächlich in der Praxis anwenden möchten. Wie benrg in den Kommentaren hervorhob , verfügen moderne x86-CPUs über eine spezielle Anweisung für diese Funktion, die schneller und genauer als dieser Hack ist. Leider
1.0 / x.sqrt()
scheint sich diese Anweisung nicht zu optimieren . Wenn Sie also wirklich die Geschwindigkeit brauchen, ist die Verwendung der_mm_rsqrt_ps
Intrinsics wahrscheinlich der richtige Weg. Dies erfordert jedoch wiederunsafe
Code. Ich werde in dieser Antwort nicht ins Detail gehen, da eine Minderheit der Programmierer sie tatsächlich brauchen wird.quelle
addss
oder verschiebtmulss
. Aber wenn die anderen 96 Bits von xmm0 ignoriert werden können, könnte man denpsrld
Befehl verwenden. Gleiches gilt für die Ganzzahlsubtraktion.fast_inv_sqrt
nur ein Newton-Raphson-Iterationsschritt, um eine bessere Annäherung an zu findeninv_sqrt
. An diesem Teil ist nichts Unsicheres. Der Trick ist im ersten Teil, der eine gute Annäherung findet. Das funktioniert, weil es eine ganzzahlige Division durch 2 auf dem Exponententeil dessqrt(pow(0.5,x))=pow(0.5,x/2)
movd
zu EAX und zurück ist eine verpasste Optimierung durch aktuelle Compiler. (Und ja, das Aufrufen von Konventionen übergibt / gibt den Skalarfloat
im Low-Element eines XMM durch und lässt zu, dass High-Bits Müll sind. Beachten Sie jedoch , dass dies leicht so bleiben kann , wenn es auf Null erweitert wurde: Rechtsverschiebung führt nicht zu Nicht-Verschiebungen. Null Elemente und auch keine Subtraktion von_mm_set_epi32(0,0,0,0x5f3759df)
, dh einemovd
Last. Sie müsstenmovdqa xmm1,xmm0
die Registrierung vorher kopierenpsrld
. Die Umgehungslatenz von der Weiterleitung des FP-Befehls zur Ganzzahl und umgekehrt wird durch diemulss
Latenz verborgen .Dieser ist mit weniger bekannt
union
in Rust implementiert :Habe einige Micro-Benchmarks mit
criterion
crate auf einer x86-64-Linux-Box durchgeführt. Überraschenderweise ist Rusts eigenesqrt().recip()
die schnellste. Aber natürlich sollte jedes Mikro-Benchmark-Ergebnis mit einem Körnchen Salz aufgenommen werden.quelle
sqrt().inv()
ist am schnellsten. Sowohl sqrt als auch inv sind heutzutage einzelne Anweisungen und gehen ziemlich schnell. Doom wurde in den Tagen geschrieben, als es nicht sicher war anzunehmen, dass es überhaupt Hardware-Gleitkomma gab, und transzendentale Funktionen wie sqrt wären definitiv Software gewesen. +1 für die Benchmarks.transmute
es sich anscheinend vonto_
und unterscheidetfrom_bits
- ich würde erwarten, dass diese bereits vor der Optimierung den Anweisungen entsprechen.Sie können verwenden
std::mem::transmute
, um die erforderliche Konvertierung vorzunehmen:Hier können Sie nach einem Live-Beispiel suchen: hier
quelle
f32::to_bits
und neu zu schreibenf32::from_bits
. Es trägt auch die Absicht eindeutig im Gegensatz zur Umwandlung, die die meisten Menschen wahrscheinlich als "Magie" betrachten.unsafe
sollte hier vermieden werden, da es nicht notwendig ist.