Implementare la radice quadrata inversa veloce in Javascript?


11

Il Fast Inverse Square Root di Quake III sembra usare un trucco a virgola mobile. A quanto ho capito, la rappresentazione in virgola mobile può avere diverse implementazioni.

Quindi è possibile implementare il Fast Inverse Square Root in Javascript?

Restituirebbe lo stesso risultato?

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;

}

Fammi sapere se questa domanda potrebbe essere posta meglio su StackOverflow. Sembrava più appropriato qui poiché ha radici di sviluppo di giochi e principalmente applicazioni di sviluppo di giochi.
Atav32,

4
Javascript ha dei puntatori?
Pubby

2
Mentre è allettante utilizzare una funzione "speciale" che accelera l'intero programma, è probabile che tu introduca dei bug o semplicemente non acceleri affatto le cose (vedi la risposta di Kevin Reid sotto per esempio). c2.com/cgi/wiki?PrematureOptimization
Daniel Carlsson,

Mi dispiace, ma usare ottimizzazioni FP di basso livello con Javascript sembra ordinare 4 hamburger grassi con patatine fritte e una dieta cola per rimanere magri. Non farlo, è inutile e ridicolo.
Nevermind,

Il sqrt inverso veloce è un'operazione molto comune nella programmazione dei giochi e tutte le console di gioco lo implementano nell'hardware. ES6 dovrebbe assolutamente considerare l'aggiunta di Math.fastinvsqrt (x) alla lingua.
John Henckel,

Risposte:


15

Il trucco dipende dalla reinterpretazione dei bit di un numero in virgola mobile come numero intero e viceversa, che è possibile in JavaScript utilizzando la funzione Array tipizzati , per creare un buffer di byte non elaborato con più viste numeriche su di esso.

Ecco una conversione letterale del codice che hai dato; si noti che non è esattamente lo stesso, poiché tutte le operazioni aritmetiche in JavaScript sono a virgola mobile a 64 bit, non a 32 bit, quindi l'input verrà necessariamente convertito. Inoltre, come il codice originale, questo dipende dalla piattaforma in quanto darà risultati senza senso se l'architettura del processore utilizza un ordine di byte diverso; se devi fare cose del genere, ti consiglio prima che l'applicazione esegua un test case per determinare che numeri interi e float abbiano le rappresentazioni di byte che ti aspetti.

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

Ho confermato osservando un grafico che questo dà risultati numerici ragionevoli. Tuttavia, non è ovvio che ciò migliorerà affatto le prestazioni, poiché stiamo eseguendo operazioni JavaScript di alto livello. Ho eseguito benchmark sui browser che ho a portata di mano e ho scoperto che Q_rsqrt(number)impiega dal 50% all'80% del tempo impiegato da 1/sqrt(number)(Chrome, Firefox e Safari su macOS, ad aprile 2018). Ecco la mia configurazione di prova completa:

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 integerveramente? È stato anni fa, quindi non ricordo esattamente quali operazioni stavo usando, ma una volta ho scritto un parser di dati in JavaScript che avrebbe convertito una stringa di byte in una serie di numeri interi N-bit (N definito nell'intestazione). È abbastanza simile.
jhocking
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.