개요
C++ Module 02 과제의 평가를 갔다가
고정소수점의 이해를 돕기 위해 평가에서 라이브로 작성한 샘플입니다.
문제
다음과 같은 코드가 있다고 가정해 봅시다.
간단히 n을 받아 특정한 상수로 나누어 반환하는 함수입니다.
int _div(int n)
{
const int divisor = 10;
return n / divisor;
}
C
복사
성능하락 요인
하지만 위 코드는 성능하락의 요소가 있습니다.
바로 나눗셈을 사용한다는 것 입니다.
CPU의 나눗셈은 덧셈, 뺄셈의 25배 정도의 비용을 지불합니다.
개선
우리는 저 10으로 나누는 코드를 10의 역수(0.1)를 곱하는 식으로 변경할 수 있습니다.
CPU에서 곱셈은 4~5cycle, 나눗셈은 24~26cycle 정도를 소모합니다.
int _div(int n)
{
const int divisor = 10;
const int recp = 1 / divisor; //< 0
//const float recp = (float)1 / divisor; //< 0.1f
return n * recp;
}
C
복사
하지만 우리는 int로 연산을 하니 역수를 구해봤자
10의 역수인 0.1의 소수점 아래가 버림되어 0이 되어버립니다.
그러면 역수를 float 으로 연산하면 될까요?
아쉽게도 floating point 연산은 FPU 혹은 SSE를 사용하는데 int 연산보다 무척이나 느립니다.
개선 2
이는 고정소수점을 이용해 역수를 int 자료형으로 사용하여 해결할 수 있습니다.
64bit_int를 이용해 상위 32bit은 정수부로, 하위 32bit은 소수부로 활용하는 것이지요.
역수는 compile time 에 연산되므로,
우리는 한 번의 곱셈과 한 번의 비트 쉬프트만으로 이를 해결할 수 있습니다. (4~5cycle + 1cycle)
#include <stdio.h>
#include <stdint.h>
int main( void )
{
int32_t n = 123;
const int32_t divisor = 10;
const int64_t recp = ((int64_t)1 << 32) / divisor;
//000000000001 000000000000 <- (int64_t)1 << 32) //< make 1 to fixed_point
//000000000000 000110011001 <- (1 / 10) in fixed_point
int64_t tmp = n * recp;
//000000001100 010010000011 <- High_32bit : 12 Low_32bit : 0.3
int32_t result = tmp >> 32;
printf("%d", result);
return 0;
}
C
복사