Javascript에서 빠른 역 제곱근을 구현 하시겠습니까?


11

Quake III의 Fast Inverse Square Root는 부동 소수점 트릭을 사용하는 것 같습니다. 내가 이해하는 것처럼 부동 소수점 표현은 다른 구현을 가질 수 있습니다.

Javascript에서 Fast Inverse Square Root를 구현할 수 있습니까?

동일한 결과를 반환합니까?

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
자바 스크립트에 포인터가 있습니까?
Pubby

2
전체 프로그램의 속도를 높이는 "특별한"기능을 사용하려는 유혹이 있지만 버그가 발생하거나 전혀 속도를 올리지 않을 가능성이 있습니다 (아래의 Kevin Reid의 답변 참조). c2.com/cgi/wiki
Daniel Carlsson

죄송합니다. 자바 스크립트로 저수준 FP 최적화를 사용하면 감자 튀김과 다이어트 콜라를 곁들인 뚱뚱한 햄버거 4 개를 주문하는 것처럼 보입니다 . 그렇게하지 마십시오. 그것은 무의미하고 말도 안됩니다.
Nevermind

빠른 역 sqrt는 게임 프로그래밍에서 매우 일반적인 작업이며 모든 게임 콘솔이이를 하드웨어로 구현합니다. ES6는 언어에 Math.fastinvsqrt (x)를 추가하는 것을 고려해야합니다.
존 헨켈

답변:


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 작업을 수행하고 있기 때문에 성능이 전혀 향상되지는 않을 것입니다. 편리한 브라우저에서 벤치 마크를 실행 Q_rsqrt(number)했으며 1/sqrt(number)(2018 년 4 월 현재 macOS의 Chrome, Firefox 및 Safari) 소요 시간의 50 % ~ 80 %가 소요됩니다 . 다음은 완전한 테스트 설정입니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.