Rust에서 Quake의 빠른 InvSqrt () 함수를 작성할 수 있습니까?


101

이것은 단지 내 자신의 호기심을 만족시키기위한 것입니다.

이것의 구현이 있습니까?

float InvSqrt (float x)
{
   float xhalf = 0.5f*x;
   int i = *(int*)&x;
   i = 0x5f3759df - (i>>1);
   x = *(float*)&i;
   x = x*(1.5f - xhalf*x*x);
   return x;
}

녹에? 존재하는 경우 코드를 게시하십시오.

나는 그것을 시도하고 실패했다. 정수 형식을 사용하여 부동 숫자를 인코딩하는 방법을 모르겠습니다. 내 시도는 다음과 같습니다.

fn main() {
    println!("Hello, world!");
    println!("sqrt1: {}, ",sqrt2(100f64));
}

fn sqrt1(x: f64) -> f64 {
    x.sqrt()
}

fn sqrt2(x: f64) -> f64 {
    let mut x = x;
    let xhalf = 0.5*x;
    let mut i = x as i64;
    println!("sqrt1: {}, ", i);

    i = 0x5f375a86 as i64 - (i>>1);

    x = i as f64;
    x = x*(1.5f64 - xhalf*x*x);
    1.0/x
}

참조 :
1. Quake3의 빠른 InvSqrt ()의 기원-1 페이지
2. Quake의 빠른 역 제곱근 이해
3. FAST INVERSE SQUARE ROOT.pdf
4. 소스 코드 : q_math.c # L552-L572



4
내가 이해하는 것처럼이 코드는 엄격한 앨리어싱 규칙 을 위반하여 C에서 UB입니다 . 이런 종류의 punning을 수행하는 표준 축복 방법은입니다 union.
trentcl

4
@ trentcl : 나도 union작동 하지 않는다고 생각 합니다. memcpy장황하지만 확실히 작동합니다.
Matthieu M.

14
@MatthieuM. 공용체로 유형 punning은 완벽하게 유효한 C 이지만 유효한 C ++은 아닙니다.
Moira

4
나는이 질문이 순수한 호기심 관점에서 문제가 없다고 생각하지만, 시간이 바뀌 었다는 것을 이해하십시오. x86 에서 1999 년 Pentium III에 도입 된 rsqrtssrsqrtps명령어는이 코드보다 빠르고 정확합니다. ARM NEON vrsqrte과 비슷한 기능이 있습니다. 그리고 Quake III가 이것을 사용한 계산은 아마도 요즘 GPU에서 수행 될 것입니다.
benrg

답변:


87

정수 형식을 사용하여 부동 숫자를 인코딩하는 방법을 모르겠습니다.

이를위한 함수가 있습니다 : f32::to_bits를 반환합니다 u32. : 다른 방향의 기능도있다 f32::from_bits소요 u32인자로는. 이러한 기능은 mem::transmute후자가 unsafe사용하기 까다롭기 때문에 선호 됩니다 .

이를 통해 다음이 구현됩니다 InvSqrt.

fn inv_sqrt(x: f32) -> f32 {
    let i = x.to_bits();
    let i = 0x5f3759df - (i >> 1);
    let y = f32::from_bits(i);

    y * (1.5 - 0.5 * x * y * y)
}

( 운동장 )


이 함수는 x86-64에서 다음 어셈블리로 컴파일됩니다.

.LCPI0_0:
        .long   3204448256        ; f32 -0.5
.LCPI0_1:
        .long   1069547520        ; f32  1.5
example::inv_sqrt:
        movd    eax, xmm0
        shr     eax                   ; i << 1
        mov     ecx, 1597463007       ; 0x5f3759df
        sub     ecx, eax              ; 0x5f3759df - ...
        movd    xmm1, ecx
        mulss   xmm0, dword ptr [rip + .LCPI0_0]    ; x *= 0.5
        mulss   xmm0, xmm1                          ; x *= y
        mulss   xmm0, xmm1                          ; x *= y
        addss   xmm0, dword ptr [rip + .LCPI0_1]    ; x += 1.5
        mulss   xmm0, xmm1                          ; x *= y
        ret

참조 어셈블리를 찾지 못했습니다 (있는 경우 알려주세요!). 왜 부동 소수점이 eax시프트 및 정수 뺄셈을하기 위해 이동했는지 잘 모르겠습니다 . SSE 레지스터가 이러한 작업을 지원하지 않을 수 있습니까?

clang 9.0 with -O3C 코드는 기본적으로 동일한 어셈블리로 C 코드를 컴파일합니다 . 좋은 징조입니다.


실제로 이것을 실제로 사용하려면 다음을 수행하지 마십시오. benrg 이 의견에서 지적했듯이 최신 x86 CPU에는이 기능에 대한 특수 명령 이이 해킹보다 빠르고 정확합니다. 불행히도 1.0 / x.sqrt() 그 명령에 최적화되지 않는 것 같습니다 . 따라서 속도가 정말로 필요하다면 내장 함수를 사용 하는_mm_rsqrt_ps 것이 좋습니다. 그러나 이것은 다시 unsafe코드를 요구 합니다. 소수의 프로그래머가 실제로 필요하기 때문에이 답변에 대해서는 자세히 설명하지 않습니다.


4
인텔 내장 함수 안내서에 따르면 만에 128 비트 레지스터 아날로그의 최하위 32 비트 시프트 어떤 정수 시프트 연산 없다 addss거나 mulss. 그러나 x96의 다른 96 비트를 무시할 수 있으면 psrld명령을 사용할 수 있습니다 . 정수 빼기도 마찬가지입니다.
fsasm

나는 녹에 대해 아무것도 아는 것을 인정하지만, 기본적으로 fast_inv_sqrt의 핵심 속성 인 "안전하지 않은"것은 아닌가? 데이터 유형 등을 완전히 무시합니다.
글로우 아이

12
@Gloweye 그것은 우리가 이야기하는 "안전하지 않은"다른 유형입니다. 정의되지 않은 동작으로 빠르고 느슨하게 연주되는 것과 비교하여 스위트 스폿에서 너무 멀리 떨어진 나쁜 값을 얻는 빠른 근사치.
중복 제거기

8
@Gloweye : 수학적으로 마지막 부분은 fast_inv_sqrt더 나은 근사값을 찾기위한 뉴턴-라프 슨 반복 단계입니다 inv_sqrt. 그 부분에 대해 안전하지 않은 것은 없습니다. 속임수는 첫 번째 부분에 있으며 좋은 근사치를 찾습니다. 이 플로트의 지수 부분에 2 정수 분할을하고, 실제로 있기 때문에 작동sqrt(pow(0.5,x))=pow(0.5,x/2)
MSalters

1
@fsasm : 맞습니다. movdEAX로 돌아가는 것은 현재 컴파일러가 놓친 최적화입니다. (그리고 네, 호출 규칙은 / 반품 스칼라을 통과 float이 경우 것이라는 XMM의 낮은 요소에 높은 비트가 쓰레기가 될 수 있도록 그러나 주. 했습니다 제로 - 확장, 쉽게 그 상태를 유지 할 수 있습니다 바로 이동이 비 소개하지 않습니다 제로 요소와 어느 쪽도 없습니다에서 빼기 않는 _mm_set_epi32(0,0,0,0x5f3759df), 즉 movd부하 당신은 필요합니다. movdqa xmm1,xmm0전에 등록을 복사하는 psrld바이 패스 지연 FP 명령 전달의 정수와 그 반대의 경우도 마찬가지으로 숨겨져 있습니다. mulss지연.
피터 코르을

37

이것은 unionRust에서 덜 알려진 것으로 구현되었습니다 .

union FI {
    f: f32,
    i: i32,
}

fn inv_sqrt(x: f32) -> f32 {
    let mut u = FI { f: x };
    unsafe {
        u.i = 0x5f3759df - (u.i >> 1);
        u.f * (1.5 - 0.5 * x * u.f * u.f)
    }
}

criterionx86-64 Linux 상자에서 크레이트를 사용하는 일부 마이크로 벤치 마크를 수행했습니다 . 놀랍게도 녹 자체 sqrt().recip()가 가장 빠릅니다. 그러나 물론 마이크로 벤치 마크 결과는 소금 한 알갱이로 가져와야합니다.

inv sqrt with transmute time:   [1.6605 ns 1.6638 ns 1.6679 ns]
inv sqrt with union     time:   [1.6543 ns 1.6583 ns 1.6633 ns]
inv sqrt with to and from bits
                        time:   [1.7659 ns 1.7677 ns 1.7697 ns]
inv sqrt with powf      time:   [7.1037 ns 7.1125 ns 7.1223 ns]
inv sqrt with sqrt then recip
                        time:   [1.5466 ns 1.5488 ns 1.5513 ns]

22
나는 sqrt().inv()가장 놀랍지 않은 것이 가장 빠릅니다. sqrt와 inv는 요즘 단일 명령어이며 꽤 빠릅니다. Doom은 하드웨어 부동 소수점이 전혀 없다고 가정하는 것이 안전하지 않은 시절에 작성되었으며 sqrt와 같은 초월 기능은 분명히 소프트웨어였습니다. 벤치 마크에서 +1
마틴 보너 모니카 지원

4
놀랍게도 그것은 transmute분명히 다른 것과는 다릅니다. to_그리고 from_bits나는 그것들이 최적화 이전에도 명령어와 동등한 것으로 기대합니다.
trentcl

2
@MartinBonner (또한 중요하지는 않지만 sqrt는 초월 함수 가 아닙니다 .)
benrg

4
@MartinBonner : 부서를 지원하는 모든 하드웨어 FPU는 일반적으로 sqrt도 지원합니다. 올바르게 반올림 된 결과를 생성하려면 IEEE "기본"작업 (+-* / sqrt)이 필요합니다. 그렇기 때문에 SSE는 모든 작업을 제공하지만 exp, sin 등은 제공하지 않습니다. 사실, split과 sqrt는 일반적으로 비슷한 방식으로 설계된 동일한 실행 단위에서 실행됩니다. 참조 HW의 사업부 / SQRT 단위 세부 사항 . 어쨌든, 특히 지연 시간에서 곱하기에 비해 여전히 빠르지 않습니다.
Peter Cordes

1
어쨌든 Skylake는 이전 uarch보다 div / sqrt의 파이프 라이닝이 훨씬 뛰어납니다. Agner Fog의 테이블에서 일부 추출에 대한 부동 소수점 나누기 대 부동 소수점 곱하기 를 참조하십시오 . 루프에서 많은 다른 작업을 수행하지 않아 sqrt + div에 병목 현상이 발생하면 쿼크 해킹 대신 HW 빠른 상호 sqrt + Newton 반복을 사용할 수 있습니다. 특히 지연 시간이 아닌 경우 처리량에 적합한 FMA의 경우. 정밀성에 따라 SSE / AVX를 사용한 빠른 벡터화 된 rsqrt 및 역수
Peter Cordes

10

std::mem::transmute필요한 변환을하기 위해 사용할 수 있습니다 :

fn inv_sqrt(x: f32) -> f32 {
    let xhalf = 0.5f32 * x;
    let mut i: i32 = unsafe { std::mem::transmute(x) };
    i = 0x5f3759df - (i >> 1);
    let mut res: f32 = unsafe { std::mem::transmute(i) };
    res = res * (1.5f32 - xhalf * res * res);
    res
}

여기에서 실제 예를 찾을 수 있습니다. 여기


4
안전하지 않은 것은 아무 문제가 없지만 명시 적 안전하지 않은 차단 없이이 작업을 수행 할 수있는 방법이 있으므로 f32::to_bitsand를 사용 하여이 답변을 다시 작성하는 것이 좋습니다 f32::from_bits. 또한 대부분의 사람들이 아마도 "마법"이라고 생각하는 변환식과는 달리 의도를 분명히 전달합니다.
Sahsahae

5
@Sahsahae 방금 언급 한 두 가지 기능을 사용하여 답변을 게시했습니다. :) unsafe필요하지 않으므로 여기서 피해야 한다는 데 동의 합니다.
Lukas Kalbertodt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.