ジョンカーマックの異常な高速逆平方根(Quake III)


112

ジョンカーマックは(float)(1.0/sqrt(x))、奇妙な0x5f3759df定数を含め、通常の4倍の速度でフロートの逆平方根を計算する、Quake IIIソースコードに特別な機能を備えています。以下のコードを参照してください。誰かがここで何が起こっているのか、なぜこれが通常の実装よりもはるかに速く機能するのかを行ごとに説明できますか?

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;
}


10
これは何兆回も書かれています。参照:google.com/search
q=0x5f3759df

15
ありがとう。これは、「C#で正の数を負にするにはどうすればよいですか」よりもはるかに興味深い質問でした。
MusiGenesis 2009

9
カーマックではなかった。 en.wikipedia.org/wiki/Fast_inverse_square_root
h4xxr 2009

7
なんてこった、これはニュートンの方法に基づくハックにすぎず、アルゴリズムの聖杯ではありません、それについて話すのをやめてください:P
ldog

回答:


75

ご参考までに。カーマックはそれを書きませんでした。Terje MathisenとGary Tarolliは両方とも、部分的(かつ非常に控えめ)なクレジットを提供しています。

神秘的な定数がどのように導出されたかは、謎のようなものです。

ゲイリータロリを引用するには:

これは実際には整数で浮動小数点計算を行っています。これがどのようにそしてなぜ機能するかを理解するのに長い時間がかかり、詳細を思い出せなくなりました。

元のアルゴリズムがどのように機能したかを解明しようとする専門の数学者(Chris Lomont)によって開発された、少し優れた定数は次のとおりです。

float InvSqrt(float x)
{
    float xhalf = 0.5f * x;
    int i = *(int*)&x;              // get bits for floating value
    i = 0x5f375a86 - (i >> 1);      // gives initial guess y0
    x = *(float*)&i;                // convert bits back to float
    x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
    return x;
}

これにもかかわらず、彼の最初の試みは、idのsqrtの数学的に「優れた」バージョン(ほぼ同じ定数になった)は、数学的にはるかに「純粋」であるにもかかわらず、Garyによって最初に開発されたものよりも劣っていました。彼は、なぜidが優れているのか説明できませんでした。


4
「数学的に純粋」とはどういう意味ですか?
タラ

1
最初の推測が恣意的であるように見えるのではなく、正当化できる定数からどこに導出できるかを想像します。技術的な説明が必要な場合は、調べることができます。私は数学者ではないので、数学用語に関する意味論的議論はSOに属していません。
Rushyo

7
それがまさに、この種のナンセンスを回避するためにその単語を恐怖の引用符でカプセル化した理由です。それは読者が口語的な英語の執筆に精通していることを前提としています。常識で十分だと思います。「Googleで2秒かかる元のソースを探すのに煩わされない人から質問されたい」と思ったので、あいまいな用語は使用しませんでした。
Rushyo、2015

2
さて、あなたは実際に質問に答えていません。
BJovke 2017

1
彼がそれを見つけた場所を知りたい人のために:beyond3d.com/content/articles/8
mr5

52

もちろん、最近では、FPUのsqrt(特に360 / PS3の場合)を使用するよりもはるかに遅いことがわかっています。浮動小数点ユニットが逆二乗を実行できる一方で、floatレジスタとintレジスタをスワップするとロードヒットストアが発生するためです。ハードウェアのルート。

基盤となるハードウェアの性質が変化するにつれて、最適化がどのように進化する必要があるかを示しています。


4
ただし、std :: sqrt()よりもはるかに高速です。
Tara

2
ソースはありますか?ランタイムをテストしたいのですが、Xbox 360開発キットがありません。
DucRP

31

Greg HewgillIllidanS4は、優れた数学的説明との関連を示しました。ここでは、あまり詳しく説明したくない人のためにまとめます。

いくつかの例外を除いて、数学関数は多項式の合計で表すことができます。

y = f(x)

正確に次のよう変換できます:

y = a0 + a1*x + a2*(x^2) + a3*(x^3) + a4*(x^4) + ...

ここで、a0、a1、a2、...は定数です。問題は、平方根などの多くの関数で、正確な値の場合、この合計のメンバー数が無限であり、一部のx ^ nで終了しないことです。しかし、いくつかのx ^ nで停止しても、ある程度の精度までの結果が得られます。

したがって、次の場合:

y = 1/sqrt(x)

この特定のケースでは、おそらく計算速度のため、2番目より上のすべての多項式メンバーを破棄することにしました。

y = a0 + a1*x + [...discarded...]

そして、タスクは、yが正確な値との差が最小になるようにa0とa1を計算するために降りてきました。最も適切な値は次のとおりであると計算されています。

a0 = 0x5f375a86
a1 = -0.5

したがって、これを方程式に入れると、次のようになります。

y = 0x5f375a86 - 0.5*x

これは、コードに表示される行と同じです。

i = 0x5f375a86 - (i >> 1);

編集:実際にここy = 0x5f375a86 - 0.5*xでは、i = 0x5f375a86 - (i >> 1);floatを整数としてシフトすると、2で除算されるだけでなく、指数も2で除算されて他のアーティファクトが発生するため、同じではありませんが、係数a0、a1、a2 ...を計算することになります。

この時点で、彼らはこの結果の精度が目的には不十分であることを発見しました。そのため、結果の精度を向上させるために、ニュートンの反復の1ステップのみを追加しました。

x = x * (1.5f - xhalf * x * x)

必要な精度が満たされるまで、ループでさらにいくつかの反復を行って、それぞれの結果を改善することができます。これはまさにCPU / FPUでの動作です!しかし、それは1回の反復で十分であるように思われ、これも速度の恩恵でした。CPU / FPUは、結果が格納される浮動小数点数の精度に到達するために必要なだけ反復を行い、すべてのケースで機能するより一般的なアルゴリズムを備えています。


要するに、彼らがしたことは:

CPU / FPUと(ほぼ)同じアルゴリズムを使用し、1 / sqrt(x)の特殊な場合の初期条件の改善を利用し、CPU / FPUが正確に行くまで計算しないで、先に停止します。計算速度の向上。


2
ポインタをlongにキャストすると、log_2(float)に近づきます。キャストバックは、2 ^ longの近似です。つまり、比率をほぼ線形にすることができます。
wizzwizz4

22

しばらく前に書かれたこの素敵な記事による ...

コードの魔法は、たとえそれに従えなくても、i = 0x5f3759df-(i >> 1);として際立っています。ライン。簡略化されたNewton-Raphsonは、推測から始まり、反復でそれを洗練する近似です。32ビットx86プロセッサーの性質を利用して、整数であるiは、最初に整数キャストを使用して、逆二乗したい浮動小数点数の値に設定されます。次に、iは0x5f3759dfに設定され、それ自体が1ビット右にシフトされます。右シフトは、iの最下位ビットを削除し、本質的に半分にします。

それは本当に良い読み物です。これはほんの一部です。


19

定数が浮動小数として何であるかを知りたくてたまらなかったので、このコードを書いて、飛び出した整数をグーグル検索しました。

    long i = 0x5F3759DF;
    float* fp = (float*)&i;
    printf("(2^127)^(1/2) = %f\n", *fp);
    //Output
    //(2^127)^(1/2) = 13211836172961054720.000000

それは定数が「浮動小数点表現の16進形式0x5f3759dfでよく知られている2 ^ 127の平方根への整数近似」のように見えますhttps://mrob.com/pub/math/numbers-18.html

同じサイトで全体を説明しています。https://mrob.com/pub/math/numbers-16.html#le009_16


6
これはもっと注目に値します。それは2 ^ 127の平方根に過ぎないことを理解した後、すべて理にかなっています...
u8y7541
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.