Впровадити швидкий зворотний квадратний корінь у Javascript?


11

Швидкий зворотний квадратний корінь від Quake III, здається, використовує трюк з плаваючою комою. Як я розумію, представлення з плаваючою комою може мати кілька різних реалізацій.

То чи можливо реалізувати швидкий зворотний квадратний корінь у 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. Тут здавалося більш доречним, оскільки він має коріння для розробників ігор та в основному ігор для розробників ігор.
Атав32

4
У Javascript є вказівники?
Паббі

2
Незважаючи на те, що спокусити використовувати "спеціальну" функцію, яка прискорює всю вашу програму, є ймовірність, що ви вводите помилки або просто не прискорюєте роботу (див., Наприклад, відповідь Кевіна Рейда нижче). c2.com/cgi/wiki?PrematureOptimization
Даніель Карлссон

Вибачте, але використання оптимізацій FP з низьким рівнем вмісту через Javascript виглядає як замовлення 4 жирних гамбургерів з картоплею фрі та дієтичною колою, щоб залишатися тонкими. Не робіть цього, це безглуздо і смішно.
Nevermind

Швидкий зворотний sqrt - це дуже поширена операція в програмуванні ігор, і всі ігрові консолі реалізують це в апаратному забезпеченні. ES6 повинен обов'язково розглянути можливість додавання Math.fastinvsqrt (x) до мови.
Джон Генкель

Відповіді:


15

Трюк залежить від переінтерпретації бітів числа з плаваючою комою як цілого числа і знову, що можливо в JavaScript за допомогою засобу " Введені масиви" , щоб створити необроблений байтовий буфер з кількома числовими видами на нього.

Ось буквальне перетворення коду, який ви дали; зауважте, що це зовсім не те саме, оскільки всі арифметичні операції в JavaScript є 64-бітовою плаваючою точкою, а не 32-бітовою, тому вхід обов’язково буде перетворений. Також, як і оригінальний код, цей залежить від платформи, оскільки він даватиме безглузді результати, якщо архітектура процесора використовує інший порядок байт; якщо ви повинні зробити такі дії, я рекомендую вашій програмі спочатку виконати тестовий випадок, щоб визначити, що цілі числа та плавці мають представлення байтів, які ви очікуєте.

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 року Q_rsqrt(number)займає від 50% до 80% часу 1/sqrt(number)(Chrome, Firefox та Safari на macOS). Ось моя повна настройка тесту:

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справді? Це було років тому, тому я не пригадую, які саме операції я використовував, але одного разу я написав аналізатор даних в JavaScript, який перетворив би рядок байтів у ряд цілих N-бітових (N було визначено в заголовку) цілих чисел. Це досить схоже.
поштовх
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.