벡터를 정규화하는 다음 두 코드를 보자.
{
scalar_t len = scalar_t(1)/length();
x *= len;
y *= len;
z *= len;
}
{
scalar_t len = length();
x /= len;
y /= len;
z /= len;
}
어느쪽이 빠를까? 언뜻보기에는 첫번째가 빨라보인다. 일반적으로 나눗셈은 곱셈보다 느리므로. 그런데 실제 50만번씩 정규화한 수행시간을 QueryPerformanceCounter로 재 보면,
45215687 : 44940973
45526767 : 45290300
46242218 : 44798457
44906158 : 46730200
임의로 5행을 뽑아봤는데, 놀랍게도, 대동소이하다. 왜 그럴까? 이 두 코드를 VC에서 기본 속도 최적화 옵션으로 컴파일하면 놀랍게도 다음과 같은 동일한 코드가 나온다.
; 78 : scalar_t len = scalar_t(1)/length();
fld DWORD PTR [ecx+8]
fld DWORD PTR [ecx+4]
fld DWORD PTR [ecx]
fld ST(0)
fmul ST(0), ST(1)
fld ST(2)
fmul ST(0), ST(3)
; 79 : x *= len;
; 80 : y *= len;
; 81 : z *= len;
faddp ST(1), ST(0)
fld ST(3)
fmul ST(0), ST(4)
faddp ST(1), ST(0)
fsqrt
fstp ST(3)
fstp
ST(0)
fstp ST(0)
fdivr DWORD PTR __real@3f800000
fld ST(0)
fmul DWORD PTR [ecx]
fstp DWORD PTR [ecx]
fld ST(0)
fmul DWORD PTR [ecx+4]
fstp DWORD PTR [ecx+4]
fmul DWORD PTR [ecx+8]
fstp DWORD PTR
[ecx+8]
; 85 : scalar_t len = length();
fld DWORD PTR [ecx+8]
fld DWORD PTR
[ecx+4]
fld DWORD PTR [ecx]
fld ST(0)
fmul ST(0),
ST(1)
fld ST(2)
fmul ST(0),
ST(3)
; 86 : x /= len;
; 87 : y /= len;
; 88 : z /= len;
faddp ST(1), ST(0)
fld ST(3)
fmul ST(0),
ST(4)
faddp ST(1), ST(0)
fsqrt
fstp ST(3)
fstp ST(0)
fstp ST(0)
fdivr DWORD
PTR __real@3f800000
fld ST(0)
fmul DWORD
PTR [ecx]
fstp DWORD PTR [ecx]
fld ST(0)
fmul DWORD
PTR [ecx+4]
fstp DWORD PTR [ecx+4]
fmul DWORD
PTR [ecx+8]
fstp DWORD PTR
[ecx+8]
코드가 같으니, 수행 시간도 사실상 동일할 수밖에. MS의 컴파일러가 제법 똑똑하게 최적화를 시켜 준다. 그런데 더 놀라운 결과는, SSE옵션을 켰을때다. SSE2 옵션을 켜고 컴파일 한 후 수행시간을 보면,
44900658 : 37913161
45154923 : 38187996
52286080 : 37779577
45221726 : 42707852
이번엔 오히려 나눗셈으로 계산을 한 쪽이 더 빠르다. 왜 그럴까? 컴파일된 결과를 보면 답이 나온다.
; _this$ = ecx
; 78 : scalar_t len = scalar_t(1)/length();
fld DWORD PTR [ecx+8]
fld DWORD PTR [ecx+4]
fld DWORD PTR
[ecx]
fld ST(0)
fmul ST(0), ST(1)
fld ST(2)
fmul ST(0), ST(3)
; 79 : x *= len;
; 80 : y *= len;
; 81 : z
*= len;
faddp ST(1), ST(0)
fld ST(3)
fmul ST(0), ST(4)
faddp ST(1), ST(0)
fsqrt
fstp ST(3)
fstp
ST(0)
fstp ST(0)
fdivr DWORD PTR __real@3f800000
fld
ST(0)
fmul DWORD PTR [ecx]
fstp DWORD PTR [ecx]
fld
ST(0)
fmul DWORD PTR [ecx+4]
fstp DWORD PTR [ecx+4]
fmul DWORD PTR [ecx+8]
fstp DWORD PTR [ecx+8]
; _this$ = ecx
; 84 : {
push ecx
; 85 : scalar_t len = length();
fld DWORD PTR [ecx+8]
; 86 : x /= len;
movss xmm0, DWORD PTR
fld DWORD PTR [ecx+4]
fld DWORD
PTR [ecx]
fld ST(0)
fmul ST(0), ST(1)
fld ST(2)
fmul ST(0), ST(3)
; 87 : y /= len;
; 88 : z /= len;
faddp ST(1), ST(0)
fld ST(3)
fmul ST(0), ST(4)
faddp ST(1), ST(0)
fsqrt
fstp ST(3)
fstp
ST(0)
fstp ST(0)
fstp DWORD PTR _len$[esp+4]
divss
xmm0, DWORD PTR _len$[esp+4]
movaps xmm1, xmm0
mulss xmm1,
DWORD PTR [ecx]
movss DWORD PTR [ecx], xmm1
movaps xmm1,
xmm0
mulss xmm1, DWORD PTR [ecx+4]
mulss xmm0, DWORD PTR
[ecx+8]
movss DWORD PTR [ecx+4], xmm1
movss DWORD PTR [ecx+8],
xmm0
; 89 : }
pop ecx
보면, 곱셈으로 계산을 한 쪽은 변화가 없고 나눗셈으로 계산을 한 쪽은 SSE명령이 생성되었다. 왜 그런지는 모른다 -_- 따라서 코드만 보고 짐작하지 말고, 직접 측정을 해봐야 한다는 결론. 컴파일러가 알아서 해주는 게 더 빠를지도 모르니까