ติดตั้งรูทสแควร์รูทแบบเร็วใน Javascript หรือไม่


11

Fast Inverse Square Root จาก Quake III ดูเหมือนจะใช้เคล็ดลับ floating-point ดังที่ฉันเข้าใจการเป็นตัวแทนจุดลอยตัวอาจมีการใช้งานที่แตกต่างกันบ้าง

ดังนั้นจึงเป็นไปได้ที่จะใช้รูทสแควร์รูทแบบเร็วใน 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 ดูเหมือนว่าจะเหมาะสมกว่าที่นี่เพราะมันมีรูทเกม dev และแอพพลิเคชั่นเกม dev ส่วนใหญ่
Atav32

4
Javascript มีพอยน์เตอร์หรือไม่?
Pubby

2
ในขณะที่มันดึงดูดการใช้ฟังก์ชั่น "พิเศษ" ที่เพิ่มความเร็วให้กับโปรแกรมทั้งหมดของคุณมีโอกาสที่คุณจะแนะนำบั๊กหรือไม่ทำสิ่งต่าง ๆ ให้เร็วขึ้น (ดูตัวอย่างคำตอบของ Kevin Reid ด้านล่าง) c2.com/cgi/wiki?PrematureOptimization
Daniel Carlsson

ฉันขอโทษ แต่การใช้การเพิ่มประสิทธิภาพ FP ในระดับต่ำกับ Javascript ดูเหมือนจะสั่งเบอร์เกอร์ไขมัน 4 รายการที่มีมันฝรั่งทอดและอาหารลดน้ำหนักเพื่อให้ผอม อย่าทำอย่างนั้นมันไร้ประโยชน์และไร้สาระ
ไม่เป็นไร

sqrt inverse ที่รวดเร็วเป็นการดำเนินการทั่วไปในการเขียนโปรแกรมเกมและคอนโซลเกมทั้งหมดใช้สิ่งนี้ในฮาร์ดแวร์ ES6 ควรพิจารณาเพิ่ม Math.fastinvsqrt (x) ในภาษาอย่างแน่นอน
John Henckel

คำตอบ:


15

เคล็ดลับขึ้นอยู่กับการตีความบิตของตัวเลขทศนิยมเป็นจำนวนเต็มและย้อนกลับมาอีกครั้งซึ่งเป็นไปได้ใน JavaScript โดยใช้สิ่งอำนวยความสะดวก Typed Arraysเพื่อสร้างบัฟเฟอร์ไบต์ที่มีมุมมองตัวเลขจำนวนมาก

นี่คือการแปลงรหัสที่คุณให้ตามตัวอักษร โปรดทราบว่ามันไม่เหมือนกันทั้งหมดเนื่องจากการดำเนินการทางคณิตศาสตร์ทั้งหมดใน 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 ในระดับสูงขึ้น ฉันใช้งานการวัดประสิทธิภาพบนเบราว์เซอร์ที่มีประโยชน์และพบว่าQ_rsqrt(number)ใช้เวลา 50% ถึง 80% ของเวลา1/sqrt(number)(Chrome, Firefox และ Safari บน macOS ณ เดือนเมษายน 2018) นี่คือการตั้งค่าการทดสอบที่สมบูรณ์ของฉัน:

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-bit (N ถูกกำหนดในส่วนหัว) มันคล้ายกันมาก
jhocking
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.