삼각함수(sin(), cos(), tan())는 엔지니어링부터 그래픽 프로그래밍 등 여러 분야에서 널리 사용되며 회전, 애니메이션 등 복잡한 수학적 연산에 필수적입니다. 특히 회전 연산에서는 특정한 각도에 대해 사용하는 경우가 자주 있으며 이런 경우를 위해 C++를 비롯한 많은 프로그래밍 언어에서는 sin()과 cos()을 직접 호출하거나 SinCos()을 호출하는 두 가지 방법이 있습니다.
SinCos() 함수는 주어진 각도에 대해 sine과 cosine 값을 동시에 계산하는 특수한 함수입니다. SinCos()는 sin()과 cos()에 비해 많이 알려져 있지 않지만 이들을 따로 호출하는 것보다 더 빠른 경우가 많아 사용해 볼 가치가 있습니다. 그러면 그 이유는 무엇일까요?
Computation overhead
하나의 점을 원점에서 30도 회전한 좌표를 계산한다고 가정해 봅시다. 회전 계산을 위해서 30도에 대한 sin()과 cos()를 호출하면 두 개의 별도 함수 호출이 필요하며, SinCos()를 호출하면 하나의 함수 호출만 필요합니다. SinCos()를 호출하는 경우와 sin()과 cos()을 따로 호출하는 경우 어떤 차이가 있을까요?
- 함수 호출 비용 감소:
함수를 호출하는 것은 메모리와 시간 면에서 비용이 발생합니다. sin()과 cos()을 따로 호출하는 경우, 두 개의 함수 호출을 수행해야 하므로 SinCos()을 호출하는 것보다 오버헤드 비용이 높아질 수 있습니다. - 메모리 액세스 절감:
sin()과 cos()을 따로 호출하는 경우, 프로세서는 메모리에서 두 개의 서로 다른 값을 로드해야 합니다. 반면, SinCos()을 호출하는 경우에는 메모리 로드 작업이 하나만 필요합니다. 이는 SinCos() 호출이 메모리 액세스를 개선해 메모리 관련 병목 현상을 줄일 수 있다는 것을 의미합니다. - 컴파일러 최적화:
현대의 컴파일러는 성능을 향상시키기 위해 코드를 최적화할 수 있습니다. 경우에 따라서는 컴파일러가 동일한 값의 sine과 cosine을 계산하고 있다는 것을 감지하고, 두 개의 별개 sin()과 cos() 호출을 자동으로 단일 SinCos() 호출로 대체할 수 있습니다. 이 최적화는 성능을 크게 향상할 수 있습니다.
sin()과 cos() 함수는 주어진 각도에 대해 결과값을 계산하기 위해 유사한 방식의 각도 계산을 수행합니다. sin()과 cos()을따로 호출하는 경우, 사실상 같은 계산을 두 번 수행하게 됩니다. 이는 계산 자원의 낭비가 될 수 있습니다. 반면, SinCos() 함수는 하나의 계산으로 같은 각도의 sine과 cosine 값을 모두 계산하기 때문에 필요한 총 계산 수를 줄일 수 있습니다.
하지만 이러한 이유들은 함수 호출 오버헤드의 일반적인 예입니다.
Sin() and Cos()의 수학적 원리
SinCos()의 연산 오버헤드와 sin(), cos()과의 성능 비교를 무시하면, SinCos()가 sin(), cos() 보다 빠른 주된 이유는 삼각함수의 수학적 원리 때문입니다.
sine과 cosine은 직각삼각형의 변과 빗변의 비율로 생각할 수 있습니다. 이런 면에서 직각삼각형의 sine은 빗변에 대한 대변의 비율이며, 각의 cosine은 빗변에 대한 인접변의 비율입니다.
수학적으로 사인과 코사인의 관계는 단순한 직각삼각형의 경우를 넘어서게 됩니다. 사인과 코사인 함수는 모든 실수에 대해 정의되며, 주기성이 2π이므로 값은 2π 단위로 반복됩니다. 단위원에서는 사인과 코사인 함수가 각도에 해당하는 원의 점의 y-좌표와 x-좌표로 정의됩니다. 단위원의 반지름은 1이므로 사인과 코사인 값은 항상 -1과 1 사이에 위치합니다.
보다 상세한 수학적 설명은 아래 링크에서 확인할 수 있습니다.
ttps://www.math-only-math.com/trigonometrical-ratios-of-90-degree-plus-theta.html
또 한가지 사인과 코사인의 가장 중요한 특성 중 하나는 상호보완성(complementary nature)입니다. 이는 주어진 각도에서 사인이 여분각의 코사인과 같으며, 그 반대도 마찬가지라는 것을 의미합니다. 예를 들어, 30도 각의 사인은 0.5이며, 60도 각의 코사인도 0.5입니다. 이 보완성 관계는 다음 이미지와 링크에서 자세히 살펴볼 수 있습니다.
삼각 함수의 구현 방식
그러나 이를 컴퓨터에서 구현하기 위해서는, 수학적 원리만으로는 계산 절차에 대한 충분한 정보를 얻을 수 없습니다. 대신, 구현하기 위한 실용적인 방법이 필요합니다.
사인과 코사인의 수학적 원리는 어떠한 플랫폼과 프레임워크에서도 동일하지만, 함수의 구현은 프로그래밍 언어, 라이브러리, 하드웨어 아키텍처에 따라 다를 수 있습니다.
예를 들어 특정 수학 라이브러리에서는 SinCos()을 호출하면 sin()과 cos() 함수를 따로 호출하여 각도의 사인과 코사인 값을 계산하기도 합니다. 그러나 일부 다른 언어나 라이브러리에서는 이러한 함수를 계산하는 다른 방법이 있을 수 있습니다.
특히 X86이나 ARM Cortex-A76과 같은 일부 하드웨어 아키텍처는 동시에 사인과 코사인 값을 계산할 수 있는 특수 명령어를 갖고 있습니다. 이러한 아키텍처에서는 SinCos() 함수의 구현이 CPU 명령어 호출로 연결되어 성능을 향상시킬 수 있습니다.
또한 어떤 플랫폼과 프레임워크에서는 사인과 코사인 함수를 계산할 때 정확도와 정밀도의 수준이 다를 수 있습니다. 이는 계산 결과에 영향을 미칠 수 있으므로 특정 응용 프로그램에서 고려해야 할 필요가 있습니다.
정확도 요구 수준
사인과 코사인 함수를 계산할 때 요구되는 정확도는 응용 분야에 따라 달라질 수 있습니다. 과학적 시뮬레이션과 엔지니어링과 같은 분야에서는 높은 수준의 정확도가 중요할 수 있으며, 게임과 같은 분야에서는 상대적으로 낮은 정확도도 허용될 수 있습니다.
과학적 시뮬레이션 및 엔지니어링 응용 분야에서는 사인과 코사인 함수의 정확도가 전체 시뮬레이션 또는 계산의 정확도에 중대한 영향을 미칠 수 있습니다. 예를 들어, 계산 유체 역학(Computational Fluid Dynamics) 시뮬레이션에서 삼각함수의 정확도는 속도, 압력, 난류와 같은 다양한 물리적 양을 계산하는 데 사용되기 때문에 전체 시뮬레이션의 정확도에 영향을 미칠 수 있습니다.
반면에 게임 응용 분야에서는 삼각함수의 정확도 요구 수준이 그렇게 높지 않을 수 있습니다. 게임에서는 정확한 수학적 결과를 도출하는 것보다 현실적이거나 시각적으로 매력적인 환경을 만드는 것이 더 중요할 수 있기 때문입니다.
게임 응용 분야에서는 sine, cosine과 같은 삼각함수를 종종 게임 객체의 움직임, 회전 및 기타 변환을 계산하는 데 사용합니다. 높은 수준의 정확도가 필요하지 않은 경우, 이러한 계산은 빠르고 효율적으로 수행되어 부드럽고 빠른 게임 경험을 유지하기 위한 방법이 되기도 합니다.
테이블 근사법
게임에서 사용되는 일반적인 최적화 기술 중 하나는 테이블 보간입니다. 이는 사전 계산된 사인 및 코사인 함수의 값을 미리 계산하여 저장한 후, 중간 각도에서 함수의 결과값을 추정하기 위해 보간을 사용하는 것입니다. 이는 처리 능력이 제한된 플랫폼에서 수학 함수를 직접 계산하는 것보다 빠를 수 있습니다.
예를 들어, sin과 cos 함수의 조회 테이블은 1도 간격으로 0에서 360도까지 범위로 미리 계산할 수 있습니다. 그런 다음 게임 중에 조회 테이블에 없는 각도에 대해 sin 또는 cos 값을 필요로 할 때, 해당 각도의 값을 보간법을 사용하여 추정합니다. 각도가 주어지면 조회 테이블에서 가장 가까운 두 각도를 찾아 해당하는 sin과 cos 값을 선형적으로 보간하여 원하는 각도의 값을 추정하는 것입니다.
테이블 보간은 게임 응용 분야에서 삼각함수 계산에 상당한 성능 향상을 제공할 수 있습니다. 특히 처리 능력이 제한된 플랫폼에서 유용합니다. 그러나 정확도와 메모리 사용량을 균형있게 조정하기 위해 조회 테이블의 범위와 해상도를 신중하게 선택해야 합니다.
Unreal Engine의 FMath::SinCos()
게임 응용 분야에서 삼각함수에 대한 테이블 보간은 일반적인 최적화 기술이지만, 일부 게임 엔진은 성능을 향상시키기 위해 또 다른 방법을 사용할 수 있습니다.
언리얼 엔진은 SinCos() 함수를 계산하기 위해 다른 접근 방식을 사용합니다. 테이블 근사법 대신, 각도의 사인 및 코사인 값을 동시에 계산하는 특수한 알고리즘을 구현합니다.
이 방식에서는, Sine Cosine의 그래프와 유사한 다항식을 근사하는 방식으로 테이블을 사용하지 않으면서도, 결과 간격에 대한 오차가 최소화된 다항식을 사용하여 필요한 정밀도를 얻습니다. 자세한 설명을 보려면 다음 링크를 참조하세요.
https://gist.github.com/publik-void/067f7f2fef32dbe5c27d6e215f824c91
언리얼 엔진의 수학 라이브러리에서 SinCos()의 구현은 이러한 다항식 근사법을 활용하며, 하나의 주어진 각도에 대해 복소 지수 계산(complex exponential evaluation)을 사용하여 각도의 사인 및 코사인 값을 동시에 계산하는 특수한 알고리즘을 사용합니다. 이는 개별적으로 함수를 계산하거나 테이블 보간을 사용하는 것보다 성능적으로 훨씬 효율적이며, 특히 빈번하게 호출되거나 성능에 중요한 계산에 대해서 유용합니다.
// UnrealMathUtility.h
/**
* Computes the sine and cosine of a scalar value.
*
* @param ScalarSin Pointer to where the Sin result should be stored
* @param ScalarCos Pointer to where the Cos result should be stored
* @param Value input angles
*/
static FORCEINLINE void SinCos( float* ScalarSin, float* ScalarCos, float Value )
{
// Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder.
float quotient = (INV_PI*0.5f)*Value;
if (Value >= 0.0f)
{
quotient = (float)((int)(quotient + 0.5f));
}
else
{
quotient = (float)((int)(quotient - 0.5f));
}
float y = Value - (2.0f*PI)*quotient;
// Map y to [-pi/2,pi/2] with sin(y) = sin(Value).
float sign;
if (y > HALF_PI)
{
y = PI - y;
sign = -1.0f;
}
else if (y < -HALF_PI)
{
y = -PI - y;
sign = -1.0f;
}
else
{
sign = +1.0f;
}
float y2 = y * y;
// 11-degree minimax approximation
*ScalarSin = ( ( ( ( (-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f ) * y2 + 0.0083333310f ) * y2 - 0.16666667f ) * y2 + 1.0f ) * y;
// 10-degree minimax approximation
float p = ( ( ( ( -2.6051615e-07f * y2 + 2.4760495e-05f ) * y2 - 0.0013888378f ) * y2 + 0.041666638f ) * y2 - 0.5f ) * y2 + 1.0f;
*ScalarCos = sign*p;
}
위의 코드에서 언리얼 엔진의 SinCos() 구현은 게임 응용 분야에 적합합니다. 그러나 언리얼 엔진의 SinCos()의 구현이 모든 응용 분야와 플랫폼에 적합하지는 않을 수 있으며, 응용 분야의 특정 요구 사항 및 제한에 따라 다른 최적화 기술이 더 적합할 수 있음에 유의해야 합니다.
현대적인 CPU에서
현대의 많은 CPU는 삼각함수 (sin, cos)를 계산하기 위한 특수한 명령어나 라이브러리를 제공합니다. 이러한 명령어와 라이브러리는 CPU의 특정 하드웨어 아키텍처를 활용하여 빠르고 효율적인 계산을 제공할 수 있습니다.
예를 들어, 인텔 CPU는 Streaming SIMD Extensions (SSE) 명령어 집합을 제공하며, 사인 및 코사인 함수를 계산하는 명령어를 포함합니다. 이러한 명령어는 벡터 데이터에서 작동하도록 설계되어 병렬 처리와 향상된 성능을 제공할 수 있습니다.
마찬가지로 GNU Scientific Library (GSL)은 사인, 코사인 및 기타 삼각함수를 계산하기 위한 최적화된 함수 집합을 제공합니다. 이러한 함수는 다항식 근사, Chebyshev 시리즈 및 CORDIC 알고리즘을 포함한 다양한 알고리즘과 기술을 사용하여 구현됩니다.
결론
SinCos 함수의 소개로 시작한 포스트이지만, 다양한 라이브러리와 언어들에서 이러한 기능들의 최적화 사례를 찾을 수 있습니다. 3D 개발자로서 이러한 성능 최적화 기능을 잘 사용하기 위해서는 원리에서 부터 구현까지 폭넓은 이해를 가지는 것이 중요합니다.
오늘날 Sin, Cos, SinCos 등의 삼각함수들은 CPU에서 제공하는 특수한 명령어나 라이브러리를 사용하며, 개발자는 하드웨어별 최적화를 활용하여 삼각함수의 빠르고 효율적인 계산을 달성할 수 있습니다. 이는 시뮬레이션, 게임 및 과학 계산과 같은 성능 중심 응용 분야에서 특히 중요합니다.
한편으로, 응용프로그램의 계산 정확도 요구 수준에 따라서 다양한 방식으로 성능을 향상시키는 방법도 있으며 이를 적절히 활용하는 것도 마찬가지로 중요합니다.
'C++' 카테고리의 다른 글
Squared Length를 이용한 3D 좌표의 빠른 거리 비교 방법 (0) | 2023.04.22 |
---|---|
버전관리(GIT, SVN)에 친화적인 C++ 코딩 스타일 (0) | 2023.04.22 |
{ } (중괄호)를 사용한 일관된 초기화 (모던 C++) (0) | 2023.03.19 |