요즘 칩에는 벡터 레지스터라는 개념으로 메모리를 배치하는데, 이 벡터 레지스터 하나에 정수형이나 실수형 4개를 한꺼번에 담아둘 수 있다고 한다.
loop 작업시 float이나 int 형식의 4바이트 변수의 배열에 대해 하나씩 훑으며 작업할 때 굳이 하나씩 작업하지 않고 4개를 묶어서 16바이트를 벡터 형식으로 한 번에 처리할 수 있고 이것은 물론 스칼라 형식으로 하나씩 작업하는 것보다 빠르다(이론 상 최대 4배까지 빠를 수 있다).
이런 것을 Vectorization이라고 한다. 일반적으로 최신 컴파일러는 이런 Vectorization을 자동으로 수행해준다.
어떤 조건하에선 수행하지 못하는 경우가 있다. 수백만줄의 코드를 모두 체크하지는 못하기 때문에 이 체크에 실패하면 Auto Vectorization에 실패한다.
Auto Vectorization에 실패했을때 알아내는 방법은 프로그래머는 어셈블리어 코드를 확인하는 방법 밖에 없는데 이것은 어려운 일이다
그리고 Debug 빌드에서는 최종 코드를 내는 것이 아니기 때문에 최적화 기능의 상당부분이 꺼져있어서 Vectorization 또한 수행되지 않는다.
10000개 짜리 float 배열 두 개에 대한 loop 작업을 10000회를 수행했을 때의 결과물이다.
Release : x64
역으로 성능이 감소하는 케이스가 발생하기는 하지만 대부분 1%이하의 확률이다.
평균적으로 40~50%의 성능 향상을 기대할 수 있다.
Release : x86
x64 버전과 거의 비슷한 수준의 결과물을 얻을 수 있다.
반복적으로 실험한 결과 평균 성능 향상 비율을 2~10% 가량 떨어진다.
Debug
Debug 빌드에서는 최적화 기능이 꺼져있기 때문에 성능 향상이 거의 없음을 알 수 있다.
Vectorization 성능 비교 코드
#include <iostream>
#include <string>
#include <Windows.h>
using
namespace
std;
#define ARRSIZE 100000
class
Vectorization
{
private
:
int
mWorkCount;
__int64
mTotalVectorTime;
__int64
mTotalNoVectorTime;
__int64
mNowVectorTime;
__int64
mNowNoVectorTime;
float
mMinImproveTime;
float
mMaxImproveTime;
int
mPerformDecreseCase;
int
mPerformIncreseCase;
int
mTotalWorkCount;
public
:
Vectorization() : mWorkCount(0), mTotalVectorTime(0), mTotalNoVectorTime(0),
mNowVectorTime(0), mNowNoVectorTime(0), mMinImproveTime(0.f), mMaxImproveTime(0.f),
mPerformDecreseCase(0), mPerformIncreseCase(0), mTotalWorkCount(0)
{}
void
CalculateWorkTime(
float
* a,
float
* b,
int
arrsize,
bool
isVector)
{
__int64
t1;
__int64
t2;
for
(
int
i = 0; i < arrsize; i++)
{
a[i] = b[i] = i;
}
QueryPerformanceCounter((LARGE_INTEGER*)&t1);
if
(isVector)
{
#pragma loop(vector)
for
(
int
i = 0; i < arrsize; i++)
{
a[i] += b[i];
}
}
else
{
#pragma loop(no_vector)
for
(
int
i = 0; i < arrsize; i++)
{
a[i] += b[i];
}
}
QueryPerformanceCounter((LARGE_INTEGER*)&t2);
if
(isVector)
{
mTotalVectorTime += mNowVectorTime = t2 - t1;
}
else
{
mTotalNoVectorTime += mNowNoVectorTime = t2 - t1;
}
}
void
CalcNowWorkImprovePerform()
{
float
ImproveRate = 100.0f - (
float
)mNowVectorTime / (
float
)mNowNoVectorTime * 100.0f;
if
(ImproveRate < mMinImproveTime)
{
mMinImproveTime = ImproveRate;
}
else
if
(ImproveRate > mMaxImproveTime)
{
mMaxImproveTime = ImproveRate;
}
if
(ImproveRate < 0)
{
mPerformDecreseCase++;
}
else
{
mPerformIncreseCase++;
}
cout <<
"현재 벡터화 작업시간 :: "
<< mNowVectorTime << endl;
cout <<
"현재 비벡터화 작업시간 :: "
<< mNowNoVectorTime << endl;
printf
(
"현재 성능 향상 비율 :: %.2f %%\n\n"
, ImproveRate);
mTotalWorkCount++;
}
void
CalcTotalWorkImprovePerform()
{
float
improveRate = 100.0f - (
float
)mTotalVectorTime / (
float
)mTotalNoVectorTime * 100.0f;
cout <<
"총 벡터화 작업시간 :: "
<< mTotalVectorTime << endl;
cout <<
"총 비벡터화 작업시간 :: "
<< mTotalNoVectorTime << endl << endl;;
printf
(
"최저 성능 향상 비율 :: %.2f %%\n"
, mMinImproveTime);
printf
(
"최고 성능 향상 비율 :: %.2f %%\n"
, mMaxImproveTime);
printf
(
"평균 성능 향상 비율 :: %.2f %%\n\n"
, improveRate);
cout <<
"성능 감소 케이스 :: "
<< mPerformDecreseCase <<
"회"
<< endl;
printf
(
"성능 감소 케이스 비율 :: %.2f %%\n\n"
, (
float
)mPerformDecreseCase / (
float
)mTotalWorkCount * 100.f);
cout <<
"성능 향상 케이스 :: "
<< mPerformIncreseCase <<
"회"
<< endl;
printf
(
"성능 향상 케이스 비율 :: %.2f %%\n\n"
, (
float
)mPerformIncreseCase / (
float
)mTotalWorkCount * 100.f);
cout <<
"총 성능 비교 횟수 :: "
<< mTotalWorkCount <<
"회"
<< endl;
}
};
int
main()
{
Vectorization v;
float
fArr1[ARRSIZE];
float
fArr2[ARRSIZE];
for
(
int
i = 0; i < 10000; i++)
{
v.CalculateWorkTime(fArr1, fArr2, ARRSIZE,
true
);
v.CalculateWorkTime(fArr1, fArr2, ARRSIZE,
false
);
v.CalcNowWorkImprovePerform();
}
v.CalcTotalWorkImprovePerform();
}
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
[투네이션]
[Patreon]
[디스코드 채널]
'C++' 카테고리의 다른 글
[C++11] enum class (1) | 2017.07.17 |
---|---|
[C++ 11] static_assert (0) | 2017.05.23 |
[C++ 11] Range-Based For (0) | 2016.11.01 |
[C++ 11] Scoped Lock (0) | 2016.11.01 |