これは私自身の好奇心を満たすためです。
これの実装はありますか:
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;
}
さびで?存在する場合は、コードを投稿してください。
私はそれを試して失敗しました。整数形式を使用して浮動小数点数をエンコードする方法がわかりません。私の試みは次のとおりです。
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
}
参照:
1。 Quake3の高速InvSqrt()の起源-ページ1
2。 Quakeの高速逆平方根を理解する
3。 FAST INVERSE SQUARE ROOT.pdf
4。 ソースコード:q_math.c#L552-L572
整数形式を使用して浮動小数点数をエンコードする方法がわかりません。
そのための関数があります: _f32::to_bits
_ _u32
_を返します。他の方向の関数もあります: _f32::from_bits
_ 引数として_u32
_を取ります。これらの関数は_mem::transmute
_よりも優先されます。後者はunsafe
であり、使用するのが難しいからです。
それで、ここにInvSqrt
の実装があります:
_fn inv_sqrt(x: f32) -> f32 {
let i = x.to_bits();
let i = 0x5f3759df - (i >> 1);
let y = f32::from_bits(i);
y * (1.5 - 0.5 * x * y * y)
}
_
( プレイグラウンド )
この関数は、x86-64で次のアセンブリにコンパイルされます。
_.LCPI0_0:
.long 3204448256 ; f32 -0.5
.LCPI0_1:
.long 1069547520 ; f32 1.5
example::inv_sqrt:
movd eax, xmm0
shr eax ; i << 1
mov ecx, 1597463007 ; 0x5f3759df
sub ecx, eax ; 0x5f3759df - ...
movd xmm1, ecx
mulss xmm0, dword ptr [rip + .LCPI0_0] ; x *= 0.5
mulss xmm0, xmm1 ; x *= y
mulss xmm0, xmm1 ; x *= y
addss xmm0, dword ptr [rip + .LCPI0_1] ; x += 1.5
mulss xmm0, xmm1 ; x *= y
ret
_
参照アセンブリは見つかりませんでした(もしあれば、教えてください!)が、私にはかなり良いようです。シフトと整数の減算を行うためだけにフロートがeax
に移動された理由はわかりません。多分SSEレジスタはこれらの操作をサポートしていませんか?
_-O3
_を指定したclang 9.0は、Cコードを 基本的に同じアセンブリ にコンパイルします。それは良い兆候です。
実際にこれを実際に使用したい場合は、しないでください。 benrg コメントで指摘 のように、最新のx86 CPUには、このハックよりも高速で正確なこの関数用の特別な命令があります。残念ながら、1.0 / x.sqrt()
その命令に最適化されていないようです 。したがって、本当に速度が必要な場合は、 __mm_rsqrt_ps
_組み込み関数 を使用するのがおそらく方法です。ただし、これには再びunsafe
コードが必要です。少数のプログラマーが実際に必要とするので、この回答では詳しく説明しません。
これはRustであまり知られていないunion
で実装されています:
_union FI {
f: f32,
i: i32,
}
fn inv_sqrt(x: f32) -> f32 {
let mut u = FI { f: x };
unsafe {
u.i = 0x5f3759df - (u.i >> 1);
u.f * (1.5 - 0.5 * x * u.f * u.f)
}
}
_
X86-64 Linuxボックスでcriterion
クレートを使用していくつかのマイクロベンチマークを行いました。驚いたことに、Rust独自のsqrt().recip()
が最速です。ただし、当然のことながら、マイクロベンチマークの結果はすべて一粒の塩で取得する必要があります。
_inv sqrt with transmute time: [1.6605 ns 1.6638 ns 1.6679 ns]
inv sqrt with union time: [1.6543 ns 1.6583 ns 1.6633 ns]
inv sqrt with to and from bits
time: [1.7659 ns 1.7677 ns 1.7697 ns]
inv sqrt with powf time: [7.1037 ns 7.1125 ns 7.1223 ns]
inv sqrt with sqrt then recip
time: [1.5466 ns 1.5488 ns 1.5513 ns]
_
std::mem::transmute
を使用して、必要な変換を行うことができます。
fn inv_sqrt(x: f32) -> f32 {
let xhalf = 0.5f32 * x;
let mut i: i32 = unsafe { std::mem::transmute(x) };
i = 0x5f3759df - (i >> 1);
let mut res: f32 = unsafe { std::mem::transmute(i) };
res = res * (1.5f32 - xhalf * res * res);
res
}
ここで実際の例を探すことができます: here