Thực hiện Root vuông nghịch đảo nhanh trong Javascript?


11

Root Inverse Square Root từ Quake III dường như sử dụng thủ thuật dấu phẩy động. Theo tôi hiểu, biểu diễn dấu phẩy động có thể có một số triển khai khác nhau.

Vì vậy, có thể thực hiện Root Inverse Square Root nhanh trong Javascript không?

Nó sẽ trả lại kết quả tương tự?

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;

}

Hãy cho tôi biết nếu câu hỏi này sẽ được hỏi tốt hơn trên StackOverflow. Nó có vẻ thích hợp hơn ở đây vì nó có gốc dev game và chủ yếu là các ứng dụng dev game.
Atav32

4
Javascript có con trỏ?
Pubby

2
Mặc dù việc sử dụng một chức năng "đặc biệt" giúp tăng tốc toàn bộ chương trình của bạn, rất có thể là bạn giới thiệu các lỗi hoặc đơn giản là không tăng tốc mọi thứ (ví dụ, xem câu trả lời của Kevin Reid bên dưới). c2.com/cgi/wiki?PrematureOptimization
Daniel Carlsson

Tôi xin lỗi, nhưng sử dụng tối ưu hóa mức độ thấp với Javascript có vẻ như yêu cầu 4 bánh mì kẹp thịt béo với khoai tây chiên và cola ăn kiêng để giữ dáng. Đừng làm vậy, thật vô nghĩa và lố bịch.
Không bao giờ bắt đầu

Sqrt nghịch đảo nhanh là một hoạt động rất phổ biến trong lập trình trò chơi và tất cả các máy chơi game thực hiện điều này trong phần cứng. ES6 chắc chắn nên xem xét thêm Math.fastinvsqrt (x) vào ngôn ngữ.
John Henckel

Câu trả lời:


15

Thủ thuật này phụ thuộc vào việc diễn giải lại các bit của số dấu phẩy động dưới dạng số nguyên và trở lại một lần nữa, điều này có thể có trong JavaScript bằng cách sử dụng tiện ích Mảng gõ , để tạo bộ đệm byte thô với nhiều chế độ xem số trên đó.

Đây là một chuyển đổi theo nghĩa đen của mã bạn đã đưa ra; lưu ý rằng nó không hoàn toàn giống nhau, vì tất cả các phép toán số học trong JavaScript là dấu phẩy động 64 bit, không phải 32 bit, do đó, đầu vào sẽ nhất thiết phải được chuyển đổi. Ngoài ra, giống như mã gốc, điều này phụ thuộc vào nền tảng ở chỗ nó sẽ cho kết quả vô nghĩa nếu kiến ​​trúc bộ xử lý sử dụng một thứ tự byte khác nhau; nếu bạn phải làm những việc như thế này, tôi khuyên ứng dụng của bạn trước tiên phải thực hiện một trường hợp thử nghiệm để xác định rằng số nguyên và số float có các biểu diễn byte mà bạn mong đợi.

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

Tôi đã xác nhận bằng cách đánh dấu đồ thị rằng điều này cho kết quả số hợp lý. Tuy nhiên, không rõ ràng rằng điều này sẽ cải thiện hiệu suất, vì chúng tôi đang thực hiện nhiều hoạt động JavaScript cấp cao hơn. Tôi đã chạy các điểm chuẩn trên các trình duyệt mà tôi có và thấy rằng Q_rsqrt(number)mất 50% đến 80% thời gian dành cho 1/sqrt(number)(Chrome, Firefox và Safari trên macOS, kể từ tháng 4 năm 2018). Đây là thiết lập thử nghiệm hoàn chỉnh của tôi:

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 integercó thật không? Cách đây nhiều năm nên tôi không nhớ chính xác những thao tác tôi đang sử dụng, nhưng tôi đã từng viết một trình phân tích cú pháp dữ liệu bằng JavaScript để chuyển một chuỗi byte thành một chuỗi các số nguyên N-bit (N được định nghĩa trong tiêu đề). Điều đó khá giống nhau.
jhocking
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.