Ma to zaspokoić moją ciekawość.
Czy istnieje implementacja tego:
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;
}
w Rust? Jeśli istnieje, opublikuj kod.
Próbowałem i nie udało mi się. Nie wiem, jak zakodować liczbę zmiennoprzecinkową przy użyciu formatu liczb całkowitych. Oto moja próba:
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
}
Odniesienia:
1. Pochodzenie Quake3's Fast InvSqrt () - Strona 1
2. Zrozumienie Quake's Fast Inverse Square Root
3. FAST INVERSE SQUARE ROOT.pdf
4. Kod źródłowy: q_math.c # L552-L572
union
.union
działa.memcpy
zdecydowanie działa, choć jest to pełne.rsqrtss
irsqrtps
instrukcje, wprowadzone z Pentium III w 1999 roku, są szybsze i dokładniejsze niż tego kodu. ARM NEON mavrsqrte
to, co jest podobne. I niezależnie od tego, jakie obliczenia zastosował Quake III, prawdopodobnie i tak zostałyby wykonane na GPU.Odpowiedzi:
Jest na to funkcja:
f32::to_bits
która zwraca anu32
. Istnieje również funkcja dla drugiego kierunku:f32::from_bits
który przyjmujeu32
argument jako argument. Te funkcje są lepsze niżmem::transmute
te ostatnieunsafe
i są trudne w użyciu.Oto implementacja
InvSqrt
:( Plac zabaw )
Ta funkcja kompiluje się do następującego zestawu na x86-64:
Nie znalazłem żadnego zestawu referencyjnego (jeśli tak, proszę powiedz mi!), Ale wydaje mi się, że jest całkiem dobry. Nie jestem tylko pewien, dlaczego zmiennoprzecinkowe zostało przeniesione
eax
tylko po to, aby wykonać przesunięcie i odejmowanie liczb całkowitych. Może rejestry SSE nie obsługują tych operacji?clang 9.0 z
-O3
kompiluje kod C w zasadzie do tego samego zestawu . To dobry znak.Warto zauważyć, że jeśli rzeczywiście chcesz to wykorzystać w praktyce: nie rób tego. Jak zauważył Benrg w komentarzach , nowoczesne procesory x86 mają specjalną instrukcję dla tej funkcji, która jest szybsza i dokładniejsza niż ten hack. Niestety
1.0 / x.sqrt()
nie wydaje się, aby optymalizować tę instrukcję . Jeśli więc naprawdę potrzebujesz prędkości, prawdopodobnie skorzystaj z funkcji_mm_rsqrt_ps
wewnętrznych . To jednak wymaga jeszczeunsafe
kodu. Nie będę szczegółowo omawiał tej odpowiedzi, ponieważ mniejszość programistów faktycznie będzie jej potrzebować.źródło
addss
lubmulss
. Ale jeśli pozostałe 96 bitów xmm0 można zignorować, można użyćpsrld
instrukcji. To samo dotyczy odejmowania liczb całkowitych.fast_inv_sqrt
to tylko jeden krok iteracji Newtona-Raphsona, aby znaleźć lepsze przybliżenieinv_sqrt
. W tej części nie ma nic niebezpiecznego. Sztuczka znajduje się w pierwszej części, która znajduje dobre przybliżenie. To działa, ponieważ wykonuje dzielenie przez liczbę całkowitą przez 2 w części wykładniczej liczby zmiennoprzecinkowej, i rzeczywiściesqrt(pow(0.5,x))=pow(0.5,x/2)
movd
do EAX iz powrotem jest brakującą optymalizacją obecnych kompilatorów. (I tak, wywoływanie konwencji przekazuje / zwraca skalarfloat
w dolnym elemencie XMM i pozwala na wyrzucanie dużych bitów. Pamiętaj jednak, że jeśli był rozszerzony do zera, może z łatwością pozostać w ten sposób: prawe przesunięcie nie wprowadza zero elementów i żadne nie odejmuje_mm_set_epi32(0,0,0,0x5f3759df)
, tjmovd
. obciążenia. Trzeba by wcześniejmovdqa xmm1,xmm0
skopiować regpsrld
. Pominięcie opóźnienia z przekazania instrukcji FP do liczby całkowitej i odwrotnie jest ukryte przezmulss
opóźnienieTen jest zaimplementowany z mniej znanym
union
w Rust:Czy niektóre mikro testy porównawcze przy użyciu
criterion
skrzynki na komputerze z systemem Linux x86-64. Zaskakująco własnysqrt().recip()
jest najszybszy. Ale oczywiście każdy wynik mikroprocesora powinien być wzięty z odrobiną soli.źródło
sqrt().inv()
jest najszybszy. Zarówno sqrt, jak i inv są obecnie pojedynczymi instrukcjami i działają dość szybko. Doom został napisany w czasach, gdy nie było bezpiecznie zakładać, że w ogóle występuje zmiennoprzecinkowy sprzęt, a funkcje transcendentalne, takie jak sqrt, zdecydowanie byłyby oprogramowaniem. +1 za testy porównawcze.transmute
jest zupełnie różne od działaniato_
ifrom_bits
- Spodziewam się tych instrukcji równoważne nawet przed optymalizacji.Możesz użyć
std::mem::transmute
do dokonania niezbędnej konwersji:Przykład na żywo możesz znaleźć tutaj: tutaj
źródło
f32::to_bits
if32::from_bits
. Niesie też intencje wyraźnie odmienne od transmutacji, które większość ludzi prawdopodobnie uważa za „magię”.unsafe
należy tego unikać, ponieważ nie jest to konieczne.