JavaScriptで高速逆平方根を実装しますか?


11

Quake IIIのFast Inverse Square Rootは、浮動小数点トリックを使用しているようです。私が理解しているように、浮動小数点表現はいくつかの異なる実装を持つことができます。

では、Javascriptで高速逆平方根を実装することは可能ですか?

同じ結果が返されますか?

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

  return y;

}

この質問がStackOverflowでより適切に行われるかどうかをお知らせください。ゲーム開発のルーツがあり、主にゲーム開発アプリケーションがあるため、ここではより適切であるように見えました。
Atav32

4
JavaScriptにはポインタがありますか?
Pubby

2
プログラム全体を高速化する「特別な」関数を使用するのは魅力的ですが、バグを導入したり、まったく高速化しない可能性があります(たとえば、以下のKevin Reidの回答を参照)。c2.com/cgi/wiki?PrematureOptimization
Daniel Carlsson

申し訳ありませんが、Javascriptで低レベルのFP最適化を使用すると、フライドポテトとダイエットコーラが入った4つのファットハンバーガーを注文するように見えます。それをしないでください、それは無意味でばかげています。
2016年

高速逆sqrtはゲームプログラミングでは非常に一般的な操作であり、すべてのゲームコンソールはこれをハードウェアで実装します。ES6は間違いなく、Math.fastinvsqrt(x)を言語に追加することを検討する必要があります。
John Henckel

回答:


15

トリックは、浮動小数点数のビットを整数として再解釈し、それを元に戻すことに依存しています。これは、JavaScriptで型付き配列機能を使用して、複数の数値ビューを持つ生のバイトバッファーを作成することで可能です。

ここにあなたが与えたコードの文字通りの変換があります。JavaScriptのすべての算術演算は32ビットではなく64ビットの浮動小数点であるため、完全に同じではないことに注意してください。したがって、入力は必ず変換されます。また、元のコードと同様に、これはプラットフォームに依存しているため、プロセッサアーキテクチャが異なるバイトオーダーを使用すると、意味のない結果が得られます。このようなことを行う必要がある場合は、アプリケーションで最初にテストケースを実行して、整数と浮動小数点数が期待どおりのバイト表現を持っていることを確認することをお勧めします。

const bytes = new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT);
const floatView = new Float32Array(bytes);
const intView = new Uint32Array(bytes);
const threehalfs = 1.5;

function Q_rsqrt(number) {
  const x2 = number * 0.5;
  floatView[0] = number;
  intView[0] = 0x5f3759df - ( intView[0] >> 1 );
  let y = floatView[0];
  y = y * ( threehalfs - ( x2 * y * y ) );

  return y;
}

これが妥当な数値結果を与えることをグラフを目視することで確認しました。ただし、より高度なJavaScript操作を実行しているため、これによってパフォーマンスが向上するかどうかは明らかではありません。私が手元にあるブラウザーでベンチマークを実行したところ、(2018年4月の時点で、macOS上のChrome、Firefox、Safari)のQ_rsqrt(number)50%から80%の時間がかかることがわかりました1/sqrt(number)。これが私の完全なテスト設定です:

const {sqrt, min, max} = Math;

const bytes = new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT);
const floatView = new Float32Array(bytes);
const intView = new Uint32Array(bytes);
const threehalfs = 1.5;

function Q_rsqrt(number) {
  const x2 = number * 0.5;
  floatView[0] = number;
  intView[0] = 0x5f3759df - ( intView[0] >> 1 );
  let y = floatView[0];
  y = y * ( threehalfs - ( x2 * y * y ) );

  return y;
}

// benchmark
const junk = new Float32Array(1);
function time(f) {
  const t0 = Date.now();
  f();
  const t1 = Date.now();
  return t1 - t0;
}
const timenat = time(() => { 
  for (let i = 0; i < 5000000; i++) junk[0] = 1/sqrt(i)
});
const timeq = time(() => {
  for (let i = 0; i < 5000000; i++) junk[0] = Q_rsqrt(i);
});
document.getElementById("info").textContent =
  "Native square root: " + timenat + " ms\n" +
  "Q_rsqrt: " + timeq + " ms\n" +
  "Ratio Q/N: " + timeq/timenat;

// plot results
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function plot(f) {
  ctx.beginPath();
  const mid = canvas.height / 2;
  for (let i = 0; i < canvas.width; i++) {
    const x_f = i / canvas.width * 10;
    const y_f = f(x_f);
    const y_px = min(canvas.height - 1, max(0, mid - y_f * mid / 5));
    ctx[i == 0 ? "moveTo" : "lineTo"](i, y_px);
  }
  ctx.stroke();
  ctx.closePath();
}
ctx.strokeStyle = "black";
plot(x => 1/sqrt(x));
ctx.strokeStyle = "yellow";
plot(x => Q_rsqrt(x));
<pre id="info"></pre>
<canvas width="300" height="300" id="canvas"
        style="border: 1px solid black;"></canvas>


In classic JavaScript, it is not possible to... reinterpreting the bits of a floating-point number as an integer本当に?何年も前だったので、使用していた操作を正確に思い出せませんが、バイトの文字列を一連のNビット(Nはヘッダーで定義されています)整数に変換するデータパーサーをJavaScriptで書いたことがあります。それはかなり似ています。
jhocking 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.