SlideShare ist ein Scribd-Unternehmen logo
1 von 47
Downloaden Sie, um offline zu lesen
헤테로지니어스 컴퓨팅 :
CPU 에서 GPU 로 옮겨가기
NCSOFT LE Team
이권일
발표자
• NDC 자주오는 단골 발표자
• NCSOFT Linear Eternal 개발팀 근무
• 최적화 및 3D 엔진 개발 (과거형)
발표 소개
• 헤테로지니어스 컴퓨팅
• DirectX 11 Compute Shader
• 시연용 예제 설명
헤테로지니어스 컴퓨팅
• 한가지 이상의 프로세서를 내장한 시스템
– AMD의 CPU+GPU 솔루션 마켓팅 용어
– 이제는 일반적인 통합 솔루션
• CPU와 GPU를 같이 사용하는 프로그래밍
– CPU는 복잡하고 선형적인 작업에 효율적
– GPU는 병렬화 가능한 반복 작업에 효율적
CPU의 30~40%는 GPU 공간
언제부터 시작할 것인가?
• 시판중인 모든 데스크톱 프로세서들이 지원중
– 2012년 이후부터 Intel/AMD 의 모든 CPU들이 지원됨
• 스마트폰 프로세서들도 OpenCL 지원중
– 최근 OpenCL 1.2 지원 SoC 들도 계속 출시중
• Stackoverflow 의 게시물에서 트렌드를 읽자!!
– SSE vs CUDA 게시물 비율이 1 : 20 쯤 된다
– SSE/AVX 는 정말 인기 없다.
DirectCompute 로 시작합시다.
• DirectX 11 부터 포함된 GPGPU 환경
– 친숙한 DirectX 인터페이스들 사용과 HLSL 이용
– VS 2012/2013에서 Compute Shader HLSL 파일 지원
• 윈도우에서 기본 지원하며 따로 설치가 필요 없음
– Shared Memory 및 Structured Data 지원
– Windows XP 미지원으로 CUDA/OpenCL 을 원할 수도 있다.
– 그러나 GPU 프로그래밍 라이브러리로서는 제일 빈약함
• Direct3D 초기화 DirectCompute 단독 사용이 가능하다.
– 20줄 이내에 초기화 코드 작성, 전체적으로 적은 코드량
Vistual Studio 의 HLSL 지원
간단한 DirectCompte의 초기화-실행
// Initialize
CComPtr<ID3D11Device> pDevice;
CComPtr<ID3D11DeviceContext> pContext;
D3D_FEATURE_LEVEL FeatureLevel[] = { D3D_FEATURE_LEVEL_11_0, };
D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_DEBUG, FeatureLevel, 1, D3D11_SDK_VERSION, &pDevice, NULL, &pContext);
// Create Shader
CComPtr<ID3D11ComputeShader> pTestCS;
std::vector<BYTE> temp = LoadShader(_T("test.cso"));
pDevice->CreateComputeShader(temp.begin()._Ptr, temp.size(), NULL, &pTestCS);
pContext->CSSetShader(pTestCS, NULL, 0);
// Create Output Buffer
CComPtr<ID3D11Texture2D> pFloatBuffer;
CD3D11_TEXTURE2D_DESC descFloatBuffer(DXGI_FORMAT_R32G32B32A32_FLOAT, texture_width, texture_height, 1, 1, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS);
pDevice->CreateTexture2D(&descFloatBuffer, NULL, &pFloatBuffer);
CComPtr<ID3D11UnorderedAccessView> pFloatView;
CD3D11_UNORDERED_ACCESS_VIEW_DESC deescFloatView(pFloatBuffer, D3D11_UAV_DIMENSION_TEXTURE2D);
pDevice->CreateUnorderedAccessView(pFloatBuffer, &deescFloatView, &pFloatView);
ID3D11UnorderedAccessView* uoViews[] = { pFloatView };
UINT uoCounts[2] = { -1, -1 };
pContext->CSSetUnorderedAccessViews(0, _countof(uoViews), uoViews, uoCounts);
// Dispatch !!
pContext->Dispatch(texture_width/32, texture_height/32, 1);
Compute Shader 4.x vs 5.0
• CS 4.x 는 DX10 시절의 GPU 까지 대부분 지원
– groupshared 메모리 16kB에 접근 방식에 제약
– UA Resource 는 1개만 지원 가능 (출력채널 1개)
– UA Resource 포맷에 제한있음
• CS 5.0 은 일반적은 GPGPU 모델을 지원
– groupshared 메모리는 32kB 까지 지원
– 자유로운 형태의 UA Resource 는 8개까지 지원
– 텍스쳐 쓰기 가능 (2,3차원 배열 + 포맷 변환)
그래서 어느거 쓰라고요?
• 2011 년 Intel Sandy Bridge 부터 CS 4.0을 지원 (CPU + GPU)
• 2012 년 Intel Ivy Bridge 부터 CS 5.0 으로 확대 (CPU + GPU)
• 2006년 ATI HD 2000 내장 보드부터 CS 4.0 지원 (GPU)
• 2011년 AMD Llano APU 부터 CS 5.0 지원 (CPU + GPU)
• 2006년 nVIDIA GeForce 8 시리즈부터 CS 4.0 지원 (GPU)
• 2010년 nVIDIA GeForce 400 시리즈부터 CS 5.0 지원 (GPU)
GPU 프로그래밍 기초
• Muti-Threading
– CPU가 루프를 돌면서 처리하던 것을 병렬로 처리
– 한번에 32, 64개 유닛이 같은 명령을 동시에 실행함
• Shared Memory
– 쓰레드간에 공유하는 작은 크기의 메모리
– 쓰레드간 주고 받을 정보나 중복 데이터 저장
GPU Multi-Threading
// cpu_test.cpp
void func()
{
for(uint i=0; i<count; ++i)
{
c[i] = a[i] + b[i];
}
}
// gpu_test_cs.hl니
[numthreads(groupWidth, groupHeight, 1)]
void func(uint i : SV_DispatchThreadID)
{
c[i] = a[i] + b[i];
}
// gpu_test.cpp
pContext->Dispatch(count, 1, 1);
GPU 멀티 쓰레딩
Job 0 Job 1 Job 2 Job 3 Job 4 Job 5 Job 6 Job 7
Job 8 Job 9 Job 10 Job 11 Job 12 Job 13 Job 14 Job 15
Job 16 Job 17 Job 18 Job 19 Job 20 Job 21 Job 22 Job 23
Job 24 Job 25 Job 26 Job 27 Job 28 Job 29 Job 30 Job 31
Job 32 Job 33 Job 34 Job 35 Job 36 Job 37 Job 38 Job 39
Job 40 Job 41 Job 42 Job 43 Job 44 Job 45 Job 46 Job 47
Job 48 Job 49 Job 50 Job 51 Job 52 Job 53 Job 54 Job 55
Job 56 Job 57 Job 58 Job 59 Job 60 Job 61 Job 62 Job 63
CPU GPU
Job 0 Job 1 Job 2 Job 3 Job 4 Job 5 Job 6 Job 7
Job 8 Job 9 Job 10 Job 11 Job 12 Job 13 Job 14 Job 15
Job 16 Job 17 Job 18 Job 19 Job 20 Job 21 Job 22 Job 23
Job 24 Job 25 Job 26 Job 27 Job 28 Job 29 Job 30 Job 31
Job 32 Job 33 Job 34 Job 35 Job 36 Job 37 Job 38 Job 39
Job 40 Job 41 Job 42 Job 43 Job 44 Job 45 Job 46 Job 47
Job 48 Job 49 Job 50 Job 51 Job 52 Job 53 Job 54 Job 55
Job 56 Job 57 Job 58 Job 59 Job 60 Job 61 Job 62 Job 63
Single Instruction Multi Thread
• 32/64 쓰레드가 같은 명령어를 실행한다.
– 명령어 유닛 1개가 여러 개의 쓰레드를 제어
– SIMD 와 흡사하나 코드 자유도가 좀더 높다.
– Thread Group, Warp, WaveFront 등으로 불린다.
• 분기 발생시 양쪽 조건을 모두 실행한다.
– 모든 쓰레드가 if/for/while 에 들어가지 못하면 다른
쓰레드는 작동을 멈춘다.
– Group 이 다양한 분기를 탈 경우 매우 느려진다.
– 모두 실패할 경우 빠르게 Context Switching 된다.
GPU 분기
if(threadId < 16)
{
// 무엇인가 실행
do_some_things();
}
else
{
// 다른 무엇인가
실행
do_other_things();
}
Shared Memory 임시버퍼로 쓰기
• Cache 용도로 메모리 접근 최적화
– Device Memory 는 상대적으로 매우 느리다!!
– 순차 복사후 랜덤하게 Shared Memory 접근
• Shared Memory 로 Register 사용을 줄인다.
– 어느정도 부피가 있는 Temporary Data 저장
– 각종 List/Queue/Stack 을 구현해도 좋다.
쓰레드간 Shared Memory 공유하기
• 다른 쓰레드가 계산한 결과를 저장한다.
– 쓰레드간 데이터 교환에 유용하다.
– Shared Memory 가 부족하면 Device Memory도 사용
• 쓰레드들을 Thread Pool 같이 써보자
– 상황에 따라 처리하는 객체들을 바꿔서 담당한다.
– 쓰레드가 32가 넘을 경우 GroupSync() 를 해야한다.
CPU 에서 GPU 로 작업을 옮겨보자
• 지금 사용중인 코드중에 무엇이 좋을까?
– 루프를 많이 돌고 있다. (well?)
– 순서가 바뀌거나 랜덤도 잘 돌아간다. (good!!)
– 임시 메모리 사용량이 적다. (perfect!!!)
• C 로 알고리즘 테스트하기
– GPU에서도 쓸 수 있는 CPU용 알고리즘 구현
– 병렬화, 메모리 사용 줄이기, 랜덤 접근 줄이기
– GPU 디버깅이 어렵기 결과 비교용으로 쓰기
예제로 보는 GPU 프로그래밍
• Bicubic Test
– Uniform Cubic B-Spline Curve
– 이미지 편집툴에서 사용하는 확대 알고리즘
• Tiny Viewer
– Hierarchical Keyframe Animation 재생
– GPU 연산을 3D 렌더링에 직접 보내는 예제
Uniform Cubic B-Spline
• GPU Gems 2 Chapter 20
• 1개의 픽셀을 위해 16개의 픽셀을 읽음
– X 축 4번 Interpolation, Y 축 1번 Interpolation
Uniform Cubic B-Spline
(1.5, 1.5) 좌표의 값을 계산하기위해
Y=0 일때 X 축 방향으로 B-Spline 값을 계산
Y=1 일때 X 축 방향으로 B-Spline 값을 계산
Y=2 일때 X 축 방향으로 B-Spline 값을 계산
Y=3 일때 X 축 방향으로 B-Spline 값을 계산
계산한 4개 값으로 Y 축 방향으로 B-Spline
값을 계산
총 16 픽셀 읽기 + 5번의 B-Spline 계산
CPU B-Spline
// CPP B-Spline 계산 코드
const BGRA& p(int x, int y) {
if(x >= 0 && x < g_image_width && y >= 0 && y < g_image_height) {
return g_source[g_image_width*y + x];
} else {
return g_black;
}
}
float w0(float t) {
return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f);
}
float w1(float t){
return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f);
}
float w2(float t){
return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f);
}
float w3(float t){
return (1.0f / 6.0f)*(t*t*t);
}
fBGRA px(int x, int y, float dx){
return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y);
}
fBGRA pxy(int x, int y, float dx, float dy){
return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) +
w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx);
}
fBGRA pxy(int x, int y, float dx, float dy) {
return px(x, y - 1, dx)*w0(dy) + px(x, y, dx)*w1(dy) +
px(x, y + 1, dx)*w2(dy) + px(x, y + 2, dx)*w3(dy);
}
// CPP Resize 루프
void ResizeBruteForce() {
float sx = ox + (g_image_width/zoom)/2;
float sy = oy + (g_image_height/zoom)/2;
for(int y=0; y<g_screen_height; ++y) {
int v = (int)floor( (y+sy) * zoom );
float dv = ( (y+sy) * zoom ) - v;
for(int x=0; x<g_screen_width; ++x) {
int u = (int)floor( (x+sx) * zoom );
float du = ( (x+sx) * zoom ) - u;
g_screen[g_screen_width*y + x] = pxy(u, v, du, dv);
}
}
}
struct BGRA {
BGRA(BYTE r, BYTE g, BYTE b, BYTE a) : R(r), G(g), B(b), A(a) {}
struct fBGRA operator*(float v) const;
struct fBGRA operator*(int v) const;
BYTE R, G, B, A;
};
struct fBGRA {
fBGRA() {}
fBGRA(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) {}
struct fBGRA operator+(const fBGRA& v) const;
struct fBGRA operator*(float v) const;
operator struct BGRA();
float R, G, B, A;
};
BGRA* g_screen = (BGRA*)_aligned_malloc(~~~~, 16);
GPU B-Spline
// HLSL 코드
float4 p(int x, int y){
return source[int2(x, y)];
}
float w0(float t) {
return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f);
}
float w1(float t){
return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f);
}
float w2(float t){
return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f);
}
float w3(float t){
return (1.0f / 6.0f)*(t*t*t);
}
float4 px(int x, int y, float dx){
return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y);
}
float4 pxy(int x, int y, float dx, float dy){
return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) +
w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx);
}
[numthreads(g_thread_width, g_thread_height, 1)]
float4 main(uint3 dispatchThreadID : SV_DispatchThreadID)
{
int v = (int)floor((dispatchThreadID.y + sy) * zoom);
int u = (int)floor((dispatchThreadID.x + sx) * zoom);
float dv = ((dispatchThreadID.y + sy) * zoom) - v;
float du = ((dispatchThreadID.x + sx) * zoom) - u;
return pxy(u, v, du, dv);
}
// CPP 코드
void Resize()
{
float sx = ox + (g_image_width / zoom) / 2;
float sy = oy + (g_image_height / zoom) / 2;
float constants[4] = { sx, sy, zoom, 0 };
g_context->UpdateSubresource(g_constBuffer, 0, NULL, constants, 0, 0);
// Dispatch !!
g_context->Dispatch( g_screen_width / g_thread_width,
g_screen_height / g_thread_height,
1);
// GPU -> CPU 로 데이터 복사
g_context->CopyResource(g_copyBuffer, g_targetBuffer);
// 속도 측정을 위해 작업 완료 대기 -
g_context->End(g_query);
while (g_context->GetData(g_query, NULL, 0, 0) == S_FALSE) {}
}
CPU vs GPU
// CPP B-Spline 계산 코드
const BGRA& p(int x, int y) {
if(x >= 0 && x < g_image_width && y >= 0 && y < g_image_height) {
return g_source[g_image_width*y + x];
} else {
return g_black;
}
}
float w0(float t) {
return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f);
}
float w1(float t){
return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f);
}
float w2(float t){
return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f);
}
float w3(float t){
return (1.0f / 6.0f)*(t*t*t);
}
fBGRA px(int x, int y, float dx){
return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y);
}
fBGRA pxy(int x, int y, float dx, float dy){
return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) +
w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx);
}
fBGRA pxy(int x, int y, float dx, float dy) {
return px(x, y - 1, dx)*w0(dy) + px(x, y, dx)*w1(dy) +
px(x, y + 1, dx)*w2(dy) + px(x, y + 2, dx)*w3(dy);
}
// HLSL 코드
float4 p(int x, int y){
return source[int2(x, y)];
}
float w0(float t) {
return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f);
}
float w1(float t){
return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f);
}
float w2(float t){
return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f);
}
float w3(float t){
return (1.0f / 6.0f)*(t*t*t);
}
float4 px(int x, int y, float dx){
return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y);
}
float4 pxy(int x, int y, float dx, float dy){
return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) +
w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx);
}
[numthreads(g_thread_width, g_thread_height, 1)]
float4 main(uint3 dispatchThreadID : SV_DispatchThreadID)
{
int v = (int)floor((dispatchThreadID.y + sy) * zoom);
int u = (int)floor((dispatchThreadID.x + sx) * zoom);
float dv = ((dispatchThreadID.y + sy) * zoom) - v;
float du = ((dispatchThreadID.x + sx) * zoom) - u;
return pxy(u, v, du, dv);
}
CPU vs GPU
// CPP Resize 루프
void ResizeBruteForce() {
float sx = ox + (g_image_width/zoom)/2;
float sy = oy + (g_image_height/zoom)/2;
for(int y=0; y<g_screen_height; ++y) {
int v = (int)floor( (y+sy) * zoom );
float dv = ( (y+sy) * zoom ) - v;
for(int x=0; x<g_screen_width; ++x) {
int u = (int)floor( (x+sx) * zoom );
float du = ( (x+sx) * zoom ) - u;
g_screen[g_screen_width*y + x] = pxy(u, v, du, dv);
}
}
}
struct BGRA {
BGRA(BYTE r, BYTE g, BYTE b, BYTE a) : R(r), G(g), B(b), A(a) {}
struct fBGRA operator*(float v) const;
struct fBGRA operator*(int v) const;
BYTE R, G, B, A;
};
struct fBGRA {
fBGRA() {}
fBGRA(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) {}
struct fBGRA operator+(const fBGRA& v) const;
struct fBGRA operator*(float v) const;
operator struct BGRA();
float R, G, B, A;
};
BGRA* g_screen = (BGRA*)_aligned_malloc(~~~~, 16);
// CPP 코드
void Resize()
{
float sx = ox + (g_image_width / zoom) / 2;
float sy = oy + (g_image_height / zoom) / 2;
float constants[4] = { sx, sy, zoom, 0 };
g_context->UpdateSubresource(g_constBuffer, 0, NULL, constants, 0, 0);
// Dispatch !!
g_context->Dispatch( g_screen_width / g_thread_width,
g_screen_height / g_thread_height,
1);
// GPU -> CPU 로 데이터 복사
g_context->CopyResource(g_copyBuffer, g_targetBuffer);
// 속도 측정을 위해 작업 완료 대기 -
g_context->End(g_query);
while (g_context->GetData(g_query, NULL, 0, 0) == S_FALSE) {}
}
// HLSL 코드
[numthreads(g_thread_width, g_thread_height, 1)]
float4 main(uint3 dispatchThreadID : SV_DispatchThreadID)
{
int v = (int)floor((dispatchThreadID.y + sy) * zoom);
int u = (int)floor((dispatchThreadID.x + sx) * zoom);
float dv = ((dispatchThreadID.y + sy) * zoom) - v;
float du = ((dispatchThreadID.x + sx) * zoom) - u;
return pxy(u, v, du, dv);
}
Bicubic Test 최적화
• 임시 버퍼에 X 축 연산을 저장하여 최적화
– 1배 확대시 X 축 1번 Y 축 1번 씩 계산
– 2배 확대시 X 축 0.5번 Y 축 1번 씩 계산
• 최적화 한것을 SSE 로 바꾸면 4배이상 항상
– 메모리 읽기/쓰기 효율성 개선
– SSE Instricsic 으로 Pixel Encode/Decode
최적화된 B-Spline 애니메이션
(1.5, 1.5) 좌표의 값을 계산하기위해
계산에 쓰일 Y=0 ~ Y=3 까지의 X 축들을
임시버퍼에 전체 계산해둠
임시 버퍼의 X 축 정보들을 사용해서 전체
Y 축 정보를 계산
첫번째 줄을 계산할때는 임시버퍼에 Y=0,
Y=3 까지의 X 축을 채워야 하지만 그
다음부터는 필요에 따라 채우면 된다.
1:1 비율시 1픽셀 읽기 + 2번의 B-Spline 계산
1:1 비율시 0.5픽셀 읽기 + 2번의 B-Spline 계산
Bicubic Test GPU 구현
• BruteForce 구현이 SSE 최적화 보다 2배 빠르다.
– 쉐이더 코드 는 CPP 를 80%정도 복사 붙이기
– 처음의 코드에 비해 대략 40배쯤 빠르다.
• 최적화 알고리즘 적용시 SSE 보다 3.7배 빠르다.
– SSE 최적화 코드 170줄
– GPU 최적화 코드 30줄
최적화된 CPU 코드
// CPP SSE Optimized 루프
void px_sse(int y, BGRA* g_source, __m128* image_sse, __m128 result_sse[g_screen_width], int u[g_screen_width], __m128 wx_sse[g_screen_width][4], int x_loop[9])
{
if(y>=0 && y<g_image_height)
{
BGRA* image= g_source + g_image_width * y;
__m128* iter = image_sse;
for(intx=0; x<g_image_width/4; ++x)
{
__m128i current = _mm_load_si128((__m128i*)(image));
__m128i low = _mm_unpacklo_epi8(current, _mm_setzero_si128());
__m128i high = _mm_unpackhi_epi8(current, _mm_setzero_si128());
image+= 4;
*iter++ = _mm_cvtepi32_ps(_mm_unpacklo_epi16(low, _mm_setzero_si128()));
*iter++ = _mm_cvtepi32_ps(_mm_unpackhi_epi16(low, _mm_setzero_si128()));
*iter++ = _mm_cvtepi32_ps(_mm_unpacklo_epi16(high, _mm_setzero_si128()));
*iter++ = _mm_cvtepi32_ps(_mm_unpackhi_epi16(high, _mm_setzero_si128()));
}
int x=0;
for(; x<x_loop[0]; ++x)
{
result_sse[x] =g_zero4;
}
for(; x<x_loop[1]; ++x)
{
result_sse[x] = wx_sse[x][3]*image_sse[u[x]+2];
}
for(; x<x_loop[2]; ++x)
{
result_sse[x] = wx_sse[x][2]*image_sse[u[x]+1]+wx_sse[x][3]*image_sse[u[x]+2];
}
for(; x<x_loop[3]; ++x)
{
result_sse[x] = wx_sse[x][1]*image_sse[u[x]+0]+wx_sse[x][2]*image_sse[u[x]+1]+wx_sse[x][3]*image_sse[u[x]+2];
}
for(; x<x_loop[4]; ++x)
{
result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0] +wx_sse[x][2]*image_sse[u[x]+1] +wx_sse[x][3]*image_sse[u[x]+2];
}
for(; x<x_loop[5]; ++x)
{
result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0] +wx_sse[x][2]*image_sse[u[x]+1];
}
for(; x<x_loop[6]; ++x)
{
result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0];
}
for(; x<x_loop[7]; ++x)
{
result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1];
}
for(; x<x_loop[8]; ++x)
{
result_sse[x] =g_zero4;
}
}
else
{
for(intx=0; x<g_screen_width; ++x)
{
result_sse[x] =g_zero4;
}
}
}
void ResizeOptimized_sse()
{
if (g_source)
{
float sx = ox + (g_image_width/zoom)/2 + 0.5f;
float sy = oy + (g_image_height/zoom)/2 + 0.5f;
const __m128 c0_sse= _mm_set_ps( 1.0f/6.0f,4.0f/6.0f,1.0f/6.0f,0.0f/6.0f);
const __m128 c1_sse= _mm_set_ps(-3.0f/6.0f,0.0f/6.0f,3.0f/6.0f,0.0f/6.0f);
const __m128 c2_sse= _mm_set_ps( 3.0f/6.0f,-6.0f/6.0f,3.0f/6.0f,0.0f/6.0f);
const __m128 c3_sse= _mm_set_ps(-1.0f/6.0f,3.0f/6.0f,-3.0f/6.0f,1.0f/6.0f);
int u[g_screen_width];
int x_loop[9] = { 0, 0, 0, 0, g_screen_width, g_screen_width, g_screen_width, g_screen_width, g_screen_width };
__m128 wx_sse[g_screen_width][4];
for(intx=0; x<g_screen_width; ++x)
{
float fu = (x+sx) * zoom - 0.5f;
u[x] = (int)floor( fu );
//u[x]-1>=0;
if(u[x]+2 <0) x_loop[0] = x+1;
if(u[x]+1 <0) x_loop[1] = x+1;
if(u[x]+0 <0) x_loop[2] = x+1;
if(u[x]-1 <0) x_loop[3] = x+1;
//u[x]+2 <g_image_width;
if(u[x]+2 <g_image_width) x_loop[4] = x+1;
if(u[x]+1 <g_image_width) x_loop[5] = x+1;
if(u[x]+0 <g_image_width) x_loop[6] = x+1;
if(u[x]-1 <g_image_width) x_loop[7] = x+1;
__m128 t1_sse = _mm_set1_ps(fu - u[x]);
__m128 temp = c3_sse*t1_sse*t1_sse*t1_sse+ c2_sse*t1_sse*t1_sse+ c1_sse*t1_sse+ c0_sse;
wx_sse[x][0]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(3,3,3,3));
wx_sse[x][1]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(2,2,2,2));
wx_sse[x][2]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(1,1,1,1));
wx_sse[x][3]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0,0,0,0));
}
__m128* image_sse= g_image_data;
__m128* temp_sse[4] = { g_temp_data + g_screen_width * 0,
g_temp_data + g_screen_width * 1,
g_temp_data + g_screen_width * 2,
g_temp_data + g_screen_width * 3 };
__m128* swap_sse[3];
int v_last = INT_MIN;
for(inty=0; y<g_screen_height; ++y)
{
float fv = (y+sy) * zoom - 0.5f;
int v = (int)floor( fv );
switch(v - v_last)
{
case0:
break;
case1:
swap_sse[0]=temp_sse[0];
temp_sse[0] = temp_sse[1];
temp_sse[1] = temp_sse[2];
temp_sse[2] = temp_sse[3];
temp_sse[3] = swap_sse[0];
px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop);
break;
case2:
swap_sse[0]=temp_sse[0];
swap_sse[1]=temp_sse[1];
temp_sse[0] = temp_sse[2];
temp_sse[1] = temp_sse[3];
temp_sse[2] = swap_sse[0];
temp_sse[3] = swap_sse[1];
px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop);
px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop);
break;
case3:
swap_sse[0]=temp_sse[0];
swap_sse[1]=temp_sse[1];
swap_sse[2]=temp_sse[2];
temp_sse[0] = temp_sse[3];
temp_sse[1] = swap_sse[0];
temp_sse[2] = swap_sse[1];
temp_sse[3] = swap_sse[2];
px_sse(v+ 0, g_source, image_sse,temp_sse[1], u, wx_sse,x_loop);
px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop);
px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop);
break;
default:
px_sse(v- 1, g_source, image_sse,temp_sse[0], u, wx_sse,x_loop);
px_sse(v+ 0, g_source, image_sse,temp_sse[1], u, wx_sse,x_loop);
px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop);
px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop);
break;
}
v_last = v;
__m128 t1_sse = _mm_set1_ps(fv - v);
__m128 temp = c3_sse*t1_sse*t1_sse*t1_sse+ c2_sse*t1_sse*t1_sse+ c1_sse*t1_sse+ c0_sse;
__m128 wy0 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(3,3,3,3));
__m128 wy1 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(2,2,2,2));
__m128 wy2 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(1,1,1,1));
__m128 wy3 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0,0,0,0));
__m128*src0 = temp_sse[0];
__m128*src1 = temp_sse[1];
__m128*src2 = temp_sse[2];
__m128*src3 = temp_sse[3];
__m128i* dest = (__m128i*)(g_screen+g_screen_width*y);
BGRA*destTemp = g_screen + g_screen_width*y;
for(intx=0; x<g_screen_width/4; ++x)
{
int prefetch = 8;
_mm_prefetch((char*)(src0+prefetch),_MM_HINT_NTA);
_mm_prefetch((char*)(src1+prefetch),_MM_HINT_NTA);
_mm_prefetch((char*)(src2+prefetch),_MM_HINT_NTA);
_mm_prefetch((char*)(src3+prefetch),_MM_HINT_NTA);
_mm_stream_si128(
dest++,
_mm_packus_epi16(
_mm_packs_epi32(
_mm_cvtps_epi32(_mm_load_ps((const float*)(src0+0))*wy0 +_mm_load_ps((const float*)(src1+0))*wy1 +_mm_load_ps((const float*)(src2+0))*wy2 +
_mm_load_ps((const float*)(src3+0))*wy3),
_mm_cvtps_epi32(_mm_load_ps((const float*)(src0+1))*wy0 +_mm_load_ps((const float*)(src1+1))*wy1 +_mm_load_ps((const float*)(src2+1))*wy2 +
_mm_load_ps((const float*)(src3+1))*wy3)
),
_mm_packs_epi32(
_mm_cvtps_epi32(_mm_load_ps((const float*)(src0+2))*wy0 +_mm_load_ps((const float*)(src1+2))*wy1 +_mm_load_ps((const float*)(src2+2))*wy2 +
_mm_load_ps((const float*)(src3+2))*wy3),
_mm_cvtps_epi32(_mm_load_ps((const float*)(src0+3))*wy0 +_mm_load_ps((const float*)(src1+3))*wy1 +_mm_load_ps((const float*)(src2+3))*wy2 +
_mm_load_ps((const float*)(src3+3))*wy3)
)
)
);
src0 +=4;
src1 +=4;
src2 +=4;
src3 +=4;
}
}
}
}
최적화된 GPU 코드
// HLSL SSE Optimized 루프
groupshared float4 buffer2[g_thread_height + 4][g_thread_width];
float4 pxy_opt2(int v, int thread_x, int thread_y, float dv) {
return w0(dv) * buffer2[v][thread_x] + w1(dv) * buffer2[v + 1][thread_x] + w2(dv) * buffer2[v + 2][thread_x] + w3(dv) * buffer2[v + 3][thread_x];
}
[numthreads(g_thread_width, g_thread_height, 1)]
float4 main(uint2 group : : SV_GroupID, uint2 thread : SV_GroupThreadID, uint2 dispatch : SV_DispatchThreadID) {
float fu = (dispatch.x + sx) * zoom;
float fv = (dispatch.y + sy) * zoom;
int u = (int)floor(fu);
int v = (int)floor(fv);
float du = fu - u;
float dv = fv - v;
int v_first = (int)floor((group.y * g_thread_height + sy) * zoom);
int v_last = (int)floor((group.y * g_thread_height + g_thread_height - 1 + sy) * zoom);
int buffer_y = thread_y;
for (int pv = v_first - 1 + thread_y; pv <= v_last + 3; pv += g_thread_height) {
buffer2[buffer_y][thread.x] = px(u, pv, du);
buffer_y += g_thread_height;
}
GroupMemoryBarrierWithGroupSync();
return pxy_opt2(v - v_first, thread.x, thread.y, dv);
}
Bicubic Test 벤치마크
0
20
40
60
80
100
120
140
CPU BruteForce CPU Optimized CPU SSE Optimiezed Intel HD 4000 nVIDIA GTS 440 nVIDIA GTX 680
Bicubic Test Benchmark
1배 확대 2배 확대
CPU BruteForce 대비 결과
0
50
100
150
200
250
300
350
CPU BruteForce CPU Optimized CPU SSE Optimiezed Intel HD 4000 nVIDIA GTS 440 nVIDIA GTX 680
Benchmark vs CPU BruteForce
1배 확대 2배 확대
Tiny Viewer 예제
• VTFetch 가 아닌 실제 캐릭터 연산을 GPU로 대체
– 시간이 일정하지 않은 키프레임 데이터
– 여러 애니메이션 블렌딩 가능 (CPU 와 동일)
– 압축된 Key Frame, 이나 IK 구현도 가능 (CPU와 동일)
• 계산 결과는 CPU와 동일한로 렌더러 사용
– CPU + GPU 동시에 연산을 분산해서 계산 가능
– GPU 연산 결과를 CPU 에서 다시 사용 가능
키프레임 업데이트 애니메이션
for each instance:
for each animation track:
find keyframe and interpolation
local[i] = matrix(key.pos, key.rot)
for each bone hierarchy:
find parent for bone
world[i] = local[i] * world[parent]
for each target skin bone:
find bone index from skin index
skin[i] = invWorld[i] * world[bone]
GPU 에 맵핑하기
Local -> World 변환 루프 문제
World 0
World 1
World 2
World 3
World 4
World 5
World 6
Group Sync 사용하기
instance = threadId /animationCount
animation = threadId % animationCount
instance = threadId /columnWidth
column = threadId % columnWidth
instance = threadId /skinCount
column = threadId % skinCount
GroupSync
GroupSync
GroupSync
GroupSync
GroupSync
GroupSync
Matrix -> Pos/Rot 메모리 줄이기
• CPU 도 GPU도 BandWidth 에 민감하다.
– 최적화를 많이할수록 메모리 퍼포먼스가 중요
– 4x4 를 4x3 또는 tanslation, rotation 페어로 변경
• AoS 와 SoA 를 잘 골라쓰자.
– SoA 가 더 빠르다고 하지만 다수의 스트림을
다루를때는 AoS 가 더 빠를때도 있다.
– BandWidth/Bank 가 적을수록 AoS 를 선호한다.
Tiny Viwer 벤치마크
Instace 개수 1024 2048 4096 8192 16384
Intel HD 4000 Full Render 13.29 25.74 50.23 99.24
Full CPU Copy 15.72 30.27 59.26 117.05
Full CPU Animation + Copy 15.73 30.23 59.27 117.12
Full GPU Animation 14.19 27.44 53.51 105.83
1/10 Render 1.99 4.26 6.67 14.73 27.88
1/10 CPU Copy 4.38 9.41 16.13 31.25 60.69
1/10 CPU Animation + Copy 5.15 8.37 16.15 36.35 60.93
1/10 GPU Animation 3.62 5.45 11.74 21.96 40.74
1 Indices Render 0.47 1.49 0.52 2.06 1.28
1 Indices CPU Copy 3.44 5.16 9.77 19.28 37.16
1 Indices CPU Animation + Copy 5.09 6.80 18.24 24.65 49.86
1 Indices GPU Animation 1.37 2.26 4.76 7.45 14.32
nVIDIA GTS 440 Full Render 3.80 7.25 14.04 27.44 54.21
Full CPU Copy 4.01 7.63 14.80 29.07 57.45
Full CPU Animation + Copy 4.01 7.63 14.78 28.92 57.07
Full GPU Animation 4.59 8.69 16.82 32.90 65.01
nVIDIA GTX 680 Full Render 0.58 1.10 2.16 4.25 8.39
Full CPU Copy 0.76 1.47 2.87 5.70 11.39
Full CPU Animation + Copy 2.42 4.50 8.67 17.11 33.38
Full GPU Animation 0.74 1.41 2.78 5.39 10.67
0.00
10.00
20.00
30.00
40.00
50.00
60.00
1 2 3 4 5
Intel HD Graphics 4000 + 1 Indices
1 Indices Render 1 Indices CPU Copy 1 Indices CPU Animation + Copy 1 Indices GPU Animation
0.00
10.00
20.00
30.00
40.00
50.00
60.00
70.00
1 2 3 4 5
nVIDIA GTS 440 / nVIDIA GTX 680
nVIDIA GTS 440 Full Render nVIDIA GTS 440 Full CPU Copy nVIDIA GTS 440 Full CPU Animation + Copy
nVIDIA GTS 440 Full GPU Animation nVIDIA GTX 680 Full Render nVIDIA GTX 680 Full CPU Copy
nVIDIA GTX 680 Full CPU Animation + Copy nVIDIA GTX 680 Full GPU Animation
GPU 와 CPU 의 비동기 처리
CPU Animation
IdleRenderCopyIdleRender IdleRenderCopy Copy
CPU Animation Copy CPU Animation CopyCopy
Render
Idle Cmd
GPU
Animation Render GPU
Animation Render GPU
Animation Render
Idle Cmd
Idle Cmd
Idle
CPU Animation
GPU Animation
Tiny Viewer 실행 결론
• Intel U3317 + HD 4000
– CPU Copy 조차 GPU Animation 보다 느리다.
– CPU Animation 까지 하면 부하가 늘어나서 더 느려진다.
• Intel Sandy Bridge 2500 + nVIDIA GTS 440
– GPU 애니메이션 이 CPU Copy 시간에 비해 3.75 배정도 더 걸린다.
– GPU Animation 손해가 제일 큰 경우이다. CPU 부하를 나을 수 있다.
• Intel Sandy Bridge 2500 + nVIDIA GTX 680
– CPU Copy 시간과 GPU Animtion 시간이 비슷하다.
– GPU 가 CPU Animtion 결과를 기다리므로 GPU Animtion 이 항상 빠르다.
GPU 의 약점을 알아두자
• 까다로운 GPU 메모리 접근
– 대역폭은 넓지만 랜덤 접근에 강하지 않다.
– Shared Memory 도 최악의 경우 16배까지 느려진다.
• 생각보다 느린 Thread 당 연산 속도
– 클럭이 CPU의 1/4~1/5 정도, IPC도 1/5~1/8
– OOE 는 지원하지만 CPU만큼 고급은 아니다.
• CPU와 데이터와 결과를 주고 받아야한다.
– 복사 비용은 매우매우매우 비싸다. 최대한 재사용하자.
– 주고 받는 데이터 크기도 최소화 하자. (fp16, uint16)
GPU 공부하면서 어려웠던 점들
• 아직 제대로된 프로파일러가 없다
– 미묘한 메모리 패널티
– 미묘한 Occupancy 패널티
• GPU 마다 특성들이 다르다
– 누구는 AoS 가 좋고 누구는 SoA 가 좋대
– 누구는 벡터를 쓰면 좋고 누구는 그냥하는게 좋대
• Intel 내장 그래픽 카드의 버그
– 가끔 데이터를 못읽어와서 고생했다.
– nVIDIA는 잘돌아가니 거기서 해보자.
Q&A
?

Weitere ähnliche Inhalte

Was ist angesagt?

코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011Esun Kim
 
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델Seungmo Koo
 
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규ChangKyu Song
 
GPU仮想化最前線 - KVMGTとvirtio-gpu -
GPU仮想化最前線 - KVMGTとvirtio-gpu -GPU仮想化最前線 - KVMGTとvirtio-gpu -
GPU仮想化最前線 - KVMGTとvirtio-gpu -zgock
 
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014devCAT Studio, NEXON
 
NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현noerror
 
Library Operating System for Linux #netdev01
Library Operating System for Linux #netdev01Library Operating System for Linux #netdev01
Library Operating System for Linux #netdev01Hajime Tazaki
 
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍Seungmo Koo
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013Esun Kim
 
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅DongMin Choi
 
나만의 엔진 개발하기
나만의 엔진 개발하기나만의 엔진 개발하기
나만의 엔진 개발하기YEONG-CHEON YOU
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree AlgorithmMerry Merry
 
Iocp 기본 구조 이해
Iocp 기본 구조 이해Iocp 기본 구조 이해
Iocp 기본 구조 이해Nam Hyeonuk
 
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard LibraryDongMin Choi
 
Android起動周りのノウハウ
Android起動周りのノウハウAndroid起動周りのノウハウ
Android起動周りのノウハウchancelab
 
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍흥배 최
 

Was ist angesagt? (20)

코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011
 
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
 
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규
[NDC07] 게임 개발에서의 클라이언트 보안 - 송창규
 
GPU仮想化最前線 - KVMGTとvirtio-gpu -
GPU仮想化最前線 - KVMGTとvirtio-gpu -GPU仮想化最前線 - KVMGTとvirtio-gpu -
GPU仮想化最前線 - KVMGTとvirtio-gpu -
 
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
이승재, 사례로 배우는 디스어셈블리 디버깅, NDC2014
 
NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현
 
Library Operating System for Linux #netdev01
Library Operating System for Linux #netdev01Library Operating System for Linux #netdev01
Library Operating System for Linux #netdev01
 
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
 
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
 
나만의 엔진 개발하기
나만의 엔진 개발하기나만의 엔진 개발하기
나만의 엔진 개발하기
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree Algorithm
 
Iocp 기본 구조 이해
Iocp 기본 구조 이해Iocp 기본 구조 이해
Iocp 기본 구조 이해
 
Linux Namespace
Linux NamespaceLinux Namespace
Linux Namespace
 
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
 
Plan 9のお話
Plan 9のお話Plan 9のお話
Plan 9のお話
 
ゼロからはじめるKVM超入門
ゼロからはじめるKVM超入門ゼロからはじめるKVM超入門
ゼロからはじめるKVM超入門
 
Android起動周りのノウハウ
Android起動周りのノウハウAndroid起動周りのノウハウ
Android起動周りのノウハウ
 
Virtual Machine Constructions for Dummies
Virtual Machine Constructions for DummiesVirtual Machine Constructions for Dummies
Virtual Machine Constructions for Dummies
 
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
 

Ähnlich wie 헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기

7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성HyeonSeok Choi
 
Compute shader DX11
Compute shader DX11Compute shader DX11
Compute shader DX11민웅 이
 
ffmpeg optimization using CUDA
ffmpeg optimization using CUDAffmpeg optimization using CUDA
ffmpeg optimization using CUDAyyooooon
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기진현 조
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9진현 조
 
병렬프로그래밍과 Cuda
병렬프로그래밍과 Cuda병렬프로그래밍과 Cuda
병렬프로그래밍과 CudaSeok-joon Yun
 
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)khuhacker
 
이권일 Sse 를 이용한 최적화와 실제 사용 예
이권일 Sse 를 이용한 최적화와 실제 사용 예이권일 Sse 를 이용한 최적화와 실제 사용 예
이권일 Sse 를 이용한 최적화와 실제 사용 예zupet
 
[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp진현 조
 
CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기YEONG-CHEON YOU
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스Sungik Kim
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기현철 조
 
Modern gpu optimize blog
Modern gpu optimize blogModern gpu optimize blog
Modern gpu optimize blogozlael ozlael
 
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안Jeongsang Baek
 
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing SystemGCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System상현 조
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요NAVER D2
 
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoLinux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoMario Cho
 

Ähnlich wie 헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기 (20)

7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성
 
Compute shader DX11
Compute shader DX11Compute shader DX11
Compute shader DX11
 
ffmpeg optimization using CUDA
ffmpeg optimization using CUDAffmpeg optimization using CUDA
ffmpeg optimization using CUDA
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9
 
병렬프로그래밍과 Cuda
병렬프로그래밍과 Cuda병렬프로그래밍과 Cuda
병렬프로그래밍과 Cuda
 
Basic git-commands
Basic git-commandsBasic git-commands
Basic git-commands
 
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
 
이권일 Sse 를 이용한 최적화와 실제 사용 예
이권일 Sse 를 이용한 최적화와 실제 사용 예이권일 Sse 를 이용한 최적화와 실제 사용 예
이권일 Sse 를 이용한 최적화와 실제 사용 예
 
[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp
 
CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기CUDA를 게임 프로젝트에 적용하기
CUDA를 게임 프로젝트에 적용하기
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
 
Modern gpu optimize blog
Modern gpu optimize blogModern gpu optimize blog
Modern gpu optimize blog
 
Modern gpu optimize
Modern gpu optimizeModern gpu optimize
Modern gpu optimize
 
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
 
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing SystemGCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
 
HI-ARC PS 101
HI-ARC PS 101HI-ARC PS 101
HI-ARC PS 101
 
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario ChoLinux Kernel Boot Process , SOSCON 2015, By Mario Cho
Linux Kernel Boot Process , SOSCON 2015, By Mario Cho
 

헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기

  • 1. 헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기 NCSOFT LE Team 이권일
  • 2. 발표자 • NDC 자주오는 단골 발표자 • NCSOFT Linear Eternal 개발팀 근무 • 최적화 및 3D 엔진 개발 (과거형)
  • 3. 발표 소개 • 헤테로지니어스 컴퓨팅 • DirectX 11 Compute Shader • 시연용 예제 설명
  • 4. 헤테로지니어스 컴퓨팅 • 한가지 이상의 프로세서를 내장한 시스템 – AMD의 CPU+GPU 솔루션 마켓팅 용어 – 이제는 일반적인 통합 솔루션 • CPU와 GPU를 같이 사용하는 프로그래밍 – CPU는 복잡하고 선형적인 작업에 효율적 – GPU는 병렬화 가능한 반복 작업에 효율적
  • 6. 언제부터 시작할 것인가? • 시판중인 모든 데스크톱 프로세서들이 지원중 – 2012년 이후부터 Intel/AMD 의 모든 CPU들이 지원됨 • 스마트폰 프로세서들도 OpenCL 지원중 – 최근 OpenCL 1.2 지원 SoC 들도 계속 출시중 • Stackoverflow 의 게시물에서 트렌드를 읽자!! – SSE vs CUDA 게시물 비율이 1 : 20 쯤 된다 – SSE/AVX 는 정말 인기 없다.
  • 7. DirectCompute 로 시작합시다. • DirectX 11 부터 포함된 GPGPU 환경 – 친숙한 DirectX 인터페이스들 사용과 HLSL 이용 – VS 2012/2013에서 Compute Shader HLSL 파일 지원 • 윈도우에서 기본 지원하며 따로 설치가 필요 없음 – Shared Memory 및 Structured Data 지원 – Windows XP 미지원으로 CUDA/OpenCL 을 원할 수도 있다. – 그러나 GPU 프로그래밍 라이브러리로서는 제일 빈약함 • Direct3D 초기화 DirectCompute 단독 사용이 가능하다. – 20줄 이내에 초기화 코드 작성, 전체적으로 적은 코드량
  • 8. Vistual Studio 의 HLSL 지원
  • 9. 간단한 DirectCompte의 초기화-실행 // Initialize CComPtr<ID3D11Device> pDevice; CComPtr<ID3D11DeviceContext> pContext; D3D_FEATURE_LEVEL FeatureLevel[] = { D3D_FEATURE_LEVEL_11_0, }; D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_DEBUG, FeatureLevel, 1, D3D11_SDK_VERSION, &pDevice, NULL, &pContext); // Create Shader CComPtr<ID3D11ComputeShader> pTestCS; std::vector<BYTE> temp = LoadShader(_T("test.cso")); pDevice->CreateComputeShader(temp.begin()._Ptr, temp.size(), NULL, &pTestCS); pContext->CSSetShader(pTestCS, NULL, 0); // Create Output Buffer CComPtr<ID3D11Texture2D> pFloatBuffer; CD3D11_TEXTURE2D_DESC descFloatBuffer(DXGI_FORMAT_R32G32B32A32_FLOAT, texture_width, texture_height, 1, 1, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS); pDevice->CreateTexture2D(&descFloatBuffer, NULL, &pFloatBuffer); CComPtr<ID3D11UnorderedAccessView> pFloatView; CD3D11_UNORDERED_ACCESS_VIEW_DESC deescFloatView(pFloatBuffer, D3D11_UAV_DIMENSION_TEXTURE2D); pDevice->CreateUnorderedAccessView(pFloatBuffer, &deescFloatView, &pFloatView); ID3D11UnorderedAccessView* uoViews[] = { pFloatView }; UINT uoCounts[2] = { -1, -1 }; pContext->CSSetUnorderedAccessViews(0, _countof(uoViews), uoViews, uoCounts); // Dispatch !! pContext->Dispatch(texture_width/32, texture_height/32, 1);
  • 10. Compute Shader 4.x vs 5.0 • CS 4.x 는 DX10 시절의 GPU 까지 대부분 지원 – groupshared 메모리 16kB에 접근 방식에 제약 – UA Resource 는 1개만 지원 가능 (출력채널 1개) – UA Resource 포맷에 제한있음 • CS 5.0 은 일반적은 GPGPU 모델을 지원 – groupshared 메모리는 32kB 까지 지원 – 자유로운 형태의 UA Resource 는 8개까지 지원 – 텍스쳐 쓰기 가능 (2,3차원 배열 + 포맷 변환)
  • 11. 그래서 어느거 쓰라고요? • 2011 년 Intel Sandy Bridge 부터 CS 4.0을 지원 (CPU + GPU) • 2012 년 Intel Ivy Bridge 부터 CS 5.0 으로 확대 (CPU + GPU) • 2006년 ATI HD 2000 내장 보드부터 CS 4.0 지원 (GPU) • 2011년 AMD Llano APU 부터 CS 5.0 지원 (CPU + GPU) • 2006년 nVIDIA GeForce 8 시리즈부터 CS 4.0 지원 (GPU) • 2010년 nVIDIA GeForce 400 시리즈부터 CS 5.0 지원 (GPU)
  • 12. GPU 프로그래밍 기초 • Muti-Threading – CPU가 루프를 돌면서 처리하던 것을 병렬로 처리 – 한번에 32, 64개 유닛이 같은 명령을 동시에 실행함 • Shared Memory – 쓰레드간에 공유하는 작은 크기의 메모리 – 쓰레드간 주고 받을 정보나 중복 데이터 저장
  • 13. GPU Multi-Threading // cpu_test.cpp void func() { for(uint i=0; i<count; ++i) { c[i] = a[i] + b[i]; } } // gpu_test_cs.hl니 [numthreads(groupWidth, groupHeight, 1)] void func(uint i : SV_DispatchThreadID) { c[i] = a[i] + b[i]; } // gpu_test.cpp pContext->Dispatch(count, 1, 1);
  • 14. GPU 멀티 쓰레딩 Job 0 Job 1 Job 2 Job 3 Job 4 Job 5 Job 6 Job 7 Job 8 Job 9 Job 10 Job 11 Job 12 Job 13 Job 14 Job 15 Job 16 Job 17 Job 18 Job 19 Job 20 Job 21 Job 22 Job 23 Job 24 Job 25 Job 26 Job 27 Job 28 Job 29 Job 30 Job 31 Job 32 Job 33 Job 34 Job 35 Job 36 Job 37 Job 38 Job 39 Job 40 Job 41 Job 42 Job 43 Job 44 Job 45 Job 46 Job 47 Job 48 Job 49 Job 50 Job 51 Job 52 Job 53 Job 54 Job 55 Job 56 Job 57 Job 58 Job 59 Job 60 Job 61 Job 62 Job 63 CPU GPU Job 0 Job 1 Job 2 Job 3 Job 4 Job 5 Job 6 Job 7 Job 8 Job 9 Job 10 Job 11 Job 12 Job 13 Job 14 Job 15 Job 16 Job 17 Job 18 Job 19 Job 20 Job 21 Job 22 Job 23 Job 24 Job 25 Job 26 Job 27 Job 28 Job 29 Job 30 Job 31 Job 32 Job 33 Job 34 Job 35 Job 36 Job 37 Job 38 Job 39 Job 40 Job 41 Job 42 Job 43 Job 44 Job 45 Job 46 Job 47 Job 48 Job 49 Job 50 Job 51 Job 52 Job 53 Job 54 Job 55 Job 56 Job 57 Job 58 Job 59 Job 60 Job 61 Job 62 Job 63
  • 15. Single Instruction Multi Thread • 32/64 쓰레드가 같은 명령어를 실행한다. – 명령어 유닛 1개가 여러 개의 쓰레드를 제어 – SIMD 와 흡사하나 코드 자유도가 좀더 높다. – Thread Group, Warp, WaveFront 등으로 불린다. • 분기 발생시 양쪽 조건을 모두 실행한다. – 모든 쓰레드가 if/for/while 에 들어가지 못하면 다른 쓰레드는 작동을 멈춘다. – Group 이 다양한 분기를 탈 경우 매우 느려진다. – 모두 실패할 경우 빠르게 Context Switching 된다.
  • 16. GPU 분기 if(threadId < 16) { // 무엇인가 실행 do_some_things(); } else { // 다른 무엇인가 실행 do_other_things(); }
  • 17. Shared Memory 임시버퍼로 쓰기 • Cache 용도로 메모리 접근 최적화 – Device Memory 는 상대적으로 매우 느리다!! – 순차 복사후 랜덤하게 Shared Memory 접근 • Shared Memory 로 Register 사용을 줄인다. – 어느정도 부피가 있는 Temporary Data 저장 – 각종 List/Queue/Stack 을 구현해도 좋다.
  • 18. 쓰레드간 Shared Memory 공유하기 • 다른 쓰레드가 계산한 결과를 저장한다. – 쓰레드간 데이터 교환에 유용하다. – Shared Memory 가 부족하면 Device Memory도 사용 • 쓰레드들을 Thread Pool 같이 써보자 – 상황에 따라 처리하는 객체들을 바꿔서 담당한다. – 쓰레드가 32가 넘을 경우 GroupSync() 를 해야한다.
  • 19. CPU 에서 GPU 로 작업을 옮겨보자 • 지금 사용중인 코드중에 무엇이 좋을까? – 루프를 많이 돌고 있다. (well?) – 순서가 바뀌거나 랜덤도 잘 돌아간다. (good!!) – 임시 메모리 사용량이 적다. (perfect!!!) • C 로 알고리즘 테스트하기 – GPU에서도 쓸 수 있는 CPU용 알고리즘 구현 – 병렬화, 메모리 사용 줄이기, 랜덤 접근 줄이기 – GPU 디버깅이 어렵기 결과 비교용으로 쓰기
  • 20. 예제로 보는 GPU 프로그래밍 • Bicubic Test – Uniform Cubic B-Spline Curve – 이미지 편집툴에서 사용하는 확대 알고리즘 • Tiny Viewer – Hierarchical Keyframe Animation 재생 – GPU 연산을 3D 렌더링에 직접 보내는 예제
  • 21. Uniform Cubic B-Spline • GPU Gems 2 Chapter 20 • 1개의 픽셀을 위해 16개의 픽셀을 읽음 – X 축 4번 Interpolation, Y 축 1번 Interpolation
  • 22. Uniform Cubic B-Spline (1.5, 1.5) 좌표의 값을 계산하기위해 Y=0 일때 X 축 방향으로 B-Spline 값을 계산 Y=1 일때 X 축 방향으로 B-Spline 값을 계산 Y=2 일때 X 축 방향으로 B-Spline 값을 계산 Y=3 일때 X 축 방향으로 B-Spline 값을 계산 계산한 4개 값으로 Y 축 방향으로 B-Spline 값을 계산 총 16 픽셀 읽기 + 5번의 B-Spline 계산
  • 23. CPU B-Spline // CPP B-Spline 계산 코드 const BGRA& p(int x, int y) { if(x >= 0 && x < g_image_width && y >= 0 && y < g_image_height) { return g_source[g_image_width*y + x]; } else { return g_black; } } float w0(float t) { return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f); } float w1(float t){ return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f); } float w2(float t){ return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f); } float w3(float t){ return (1.0f / 6.0f)*(t*t*t); } fBGRA px(int x, int y, float dx){ return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y); } fBGRA pxy(int x, int y, float dx, float dy){ return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) + w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx); } fBGRA pxy(int x, int y, float dx, float dy) { return px(x, y - 1, dx)*w0(dy) + px(x, y, dx)*w1(dy) + px(x, y + 1, dx)*w2(dy) + px(x, y + 2, dx)*w3(dy); } // CPP Resize 루프 void ResizeBruteForce() { float sx = ox + (g_image_width/zoom)/2; float sy = oy + (g_image_height/zoom)/2; for(int y=0; y<g_screen_height; ++y) { int v = (int)floor( (y+sy) * zoom ); float dv = ( (y+sy) * zoom ) - v; for(int x=0; x<g_screen_width; ++x) { int u = (int)floor( (x+sx) * zoom ); float du = ( (x+sx) * zoom ) - u; g_screen[g_screen_width*y + x] = pxy(u, v, du, dv); } } } struct BGRA { BGRA(BYTE r, BYTE g, BYTE b, BYTE a) : R(r), G(g), B(b), A(a) {} struct fBGRA operator*(float v) const; struct fBGRA operator*(int v) const; BYTE R, G, B, A; }; struct fBGRA { fBGRA() {} fBGRA(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) {} struct fBGRA operator+(const fBGRA& v) const; struct fBGRA operator*(float v) const; operator struct BGRA(); float R, G, B, A; }; BGRA* g_screen = (BGRA*)_aligned_malloc(~~~~, 16);
  • 24. GPU B-Spline // HLSL 코드 float4 p(int x, int y){ return source[int2(x, y)]; } float w0(float t) { return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f); } float w1(float t){ return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f); } float w2(float t){ return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f); } float w3(float t){ return (1.0f / 6.0f)*(t*t*t); } float4 px(int x, int y, float dx){ return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y); } float4 pxy(int x, int y, float dx, float dy){ return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) + w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx); } [numthreads(g_thread_width, g_thread_height, 1)] float4 main(uint3 dispatchThreadID : SV_DispatchThreadID) { int v = (int)floor((dispatchThreadID.y + sy) * zoom); int u = (int)floor((dispatchThreadID.x + sx) * zoom); float dv = ((dispatchThreadID.y + sy) * zoom) - v; float du = ((dispatchThreadID.x + sx) * zoom) - u; return pxy(u, v, du, dv); } // CPP 코드 void Resize() { float sx = ox + (g_image_width / zoom) / 2; float sy = oy + (g_image_height / zoom) / 2; float constants[4] = { sx, sy, zoom, 0 }; g_context->UpdateSubresource(g_constBuffer, 0, NULL, constants, 0, 0); // Dispatch !! g_context->Dispatch( g_screen_width / g_thread_width, g_screen_height / g_thread_height, 1); // GPU -> CPU 로 데이터 복사 g_context->CopyResource(g_copyBuffer, g_targetBuffer); // 속도 측정을 위해 작업 완료 대기 - g_context->End(g_query); while (g_context->GetData(g_query, NULL, 0, 0) == S_FALSE) {} }
  • 25. CPU vs GPU // CPP B-Spline 계산 코드 const BGRA& p(int x, int y) { if(x >= 0 && x < g_image_width && y >= 0 && y < g_image_height) { return g_source[g_image_width*y + x]; } else { return g_black; } } float w0(float t) { return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f); } float w1(float t){ return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f); } float w2(float t){ return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f); } float w3(float t){ return (1.0f / 6.0f)*(t*t*t); } fBGRA px(int x, int y, float dx){ return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y); } fBGRA pxy(int x, int y, float dx, float dy){ return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) + w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx); } fBGRA pxy(int x, int y, float dx, float dy) { return px(x, y - 1, dx)*w0(dy) + px(x, y, dx)*w1(dy) + px(x, y + 1, dx)*w2(dy) + px(x, y + 2, dx)*w3(dy); } // HLSL 코드 float4 p(int x, int y){ return source[int2(x, y)]; } float w0(float t) { return (1.0f / 6.0f)*(-t*t*t + 3.0f*t*t - 3.0f*t + 1.0f); } float w1(float t){ return (1.0f / 6.0f)*(3.0f*t*t*t - 6.0f*t*t + 4.0f); } float w2(float t){ return (1.0f / 6.0f)*(-3.0f*t*t*t + 3.0f*t*t + 3.0f*t + 1.0f); } float w3(float t){ return (1.0f / 6.0f)*(t*t*t); } float4 px(int x, int y, float dx){ return w0(dx)*p(x - 1, y) + w1(dx)*p(x, y) + w2(dx)*p(x + 1, y) + w3(dx)*p(x + 2, y); } float4 pxy(int x, int y, float dx, float dy){ return w0(dy)*px(x, y - 1, dx) + w1(dy)*px(x, y, dx) + w2(dy)*px(x, y + 1, dx) + w3(dy)*px(x, y + 2, dx); } [numthreads(g_thread_width, g_thread_height, 1)] float4 main(uint3 dispatchThreadID : SV_DispatchThreadID) { int v = (int)floor((dispatchThreadID.y + sy) * zoom); int u = (int)floor((dispatchThreadID.x + sx) * zoom); float dv = ((dispatchThreadID.y + sy) * zoom) - v; float du = ((dispatchThreadID.x + sx) * zoom) - u; return pxy(u, v, du, dv); }
  • 26. CPU vs GPU // CPP Resize 루프 void ResizeBruteForce() { float sx = ox + (g_image_width/zoom)/2; float sy = oy + (g_image_height/zoom)/2; for(int y=0; y<g_screen_height; ++y) { int v = (int)floor( (y+sy) * zoom ); float dv = ( (y+sy) * zoom ) - v; for(int x=0; x<g_screen_width; ++x) { int u = (int)floor( (x+sx) * zoom ); float du = ( (x+sx) * zoom ) - u; g_screen[g_screen_width*y + x] = pxy(u, v, du, dv); } } } struct BGRA { BGRA(BYTE r, BYTE g, BYTE b, BYTE a) : R(r), G(g), B(b), A(a) {} struct fBGRA operator*(float v) const; struct fBGRA operator*(int v) const; BYTE R, G, B, A; }; struct fBGRA { fBGRA() {} fBGRA(float r, float g, float b, float a) : R(r), G(g), B(b), A(a) {} struct fBGRA operator+(const fBGRA& v) const; struct fBGRA operator*(float v) const; operator struct BGRA(); float R, G, B, A; }; BGRA* g_screen = (BGRA*)_aligned_malloc(~~~~, 16); // CPP 코드 void Resize() { float sx = ox + (g_image_width / zoom) / 2; float sy = oy + (g_image_height / zoom) / 2; float constants[4] = { sx, sy, zoom, 0 }; g_context->UpdateSubresource(g_constBuffer, 0, NULL, constants, 0, 0); // Dispatch !! g_context->Dispatch( g_screen_width / g_thread_width, g_screen_height / g_thread_height, 1); // GPU -> CPU 로 데이터 복사 g_context->CopyResource(g_copyBuffer, g_targetBuffer); // 속도 측정을 위해 작업 완료 대기 - g_context->End(g_query); while (g_context->GetData(g_query, NULL, 0, 0) == S_FALSE) {} } // HLSL 코드 [numthreads(g_thread_width, g_thread_height, 1)] float4 main(uint3 dispatchThreadID : SV_DispatchThreadID) { int v = (int)floor((dispatchThreadID.y + sy) * zoom); int u = (int)floor((dispatchThreadID.x + sx) * zoom); float dv = ((dispatchThreadID.y + sy) * zoom) - v; float du = ((dispatchThreadID.x + sx) * zoom) - u; return pxy(u, v, du, dv); }
  • 27. Bicubic Test 최적화 • 임시 버퍼에 X 축 연산을 저장하여 최적화 – 1배 확대시 X 축 1번 Y 축 1번 씩 계산 – 2배 확대시 X 축 0.5번 Y 축 1번 씩 계산 • 최적화 한것을 SSE 로 바꾸면 4배이상 항상 – 메모리 읽기/쓰기 효율성 개선 – SSE Instricsic 으로 Pixel Encode/Decode
  • 28. 최적화된 B-Spline 애니메이션 (1.5, 1.5) 좌표의 값을 계산하기위해 계산에 쓰일 Y=0 ~ Y=3 까지의 X 축들을 임시버퍼에 전체 계산해둠 임시 버퍼의 X 축 정보들을 사용해서 전체 Y 축 정보를 계산 첫번째 줄을 계산할때는 임시버퍼에 Y=0, Y=3 까지의 X 축을 채워야 하지만 그 다음부터는 필요에 따라 채우면 된다. 1:1 비율시 1픽셀 읽기 + 2번의 B-Spline 계산 1:1 비율시 0.5픽셀 읽기 + 2번의 B-Spline 계산
  • 29. Bicubic Test GPU 구현 • BruteForce 구현이 SSE 최적화 보다 2배 빠르다. – 쉐이더 코드 는 CPP 를 80%정도 복사 붙이기 – 처음의 코드에 비해 대략 40배쯤 빠르다. • 최적화 알고리즘 적용시 SSE 보다 3.7배 빠르다. – SSE 최적화 코드 170줄 – GPU 최적화 코드 30줄
  • 30. 최적화된 CPU 코드 // CPP SSE Optimized 루프 void px_sse(int y, BGRA* g_source, __m128* image_sse, __m128 result_sse[g_screen_width], int u[g_screen_width], __m128 wx_sse[g_screen_width][4], int x_loop[9]) { if(y>=0 && y<g_image_height) { BGRA* image= g_source + g_image_width * y; __m128* iter = image_sse; for(intx=0; x<g_image_width/4; ++x) { __m128i current = _mm_load_si128((__m128i*)(image)); __m128i low = _mm_unpacklo_epi8(current, _mm_setzero_si128()); __m128i high = _mm_unpackhi_epi8(current, _mm_setzero_si128()); image+= 4; *iter++ = _mm_cvtepi32_ps(_mm_unpacklo_epi16(low, _mm_setzero_si128())); *iter++ = _mm_cvtepi32_ps(_mm_unpackhi_epi16(low, _mm_setzero_si128())); *iter++ = _mm_cvtepi32_ps(_mm_unpacklo_epi16(high, _mm_setzero_si128())); *iter++ = _mm_cvtepi32_ps(_mm_unpackhi_epi16(high, _mm_setzero_si128())); } int x=0; for(; x<x_loop[0]; ++x) { result_sse[x] =g_zero4; } for(; x<x_loop[1]; ++x) { result_sse[x] = wx_sse[x][3]*image_sse[u[x]+2]; } for(; x<x_loop[2]; ++x) { result_sse[x] = wx_sse[x][2]*image_sse[u[x]+1]+wx_sse[x][3]*image_sse[u[x]+2]; } for(; x<x_loop[3]; ++x) { result_sse[x] = wx_sse[x][1]*image_sse[u[x]+0]+wx_sse[x][2]*image_sse[u[x]+1]+wx_sse[x][3]*image_sse[u[x]+2]; } for(; x<x_loop[4]; ++x) { result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0] +wx_sse[x][2]*image_sse[u[x]+1] +wx_sse[x][3]*image_sse[u[x]+2]; } for(; x<x_loop[5]; ++x) { result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0] +wx_sse[x][2]*image_sse[u[x]+1]; } for(; x<x_loop[6]; ++x) { result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]+wx_sse[x][1]*image_sse[u[x]+0]; } for(; x<x_loop[7]; ++x) { result_sse[x] =wx_sse[x][0]*image_sse[u[x]-1]; } for(; x<x_loop[8]; ++x) { result_sse[x] =g_zero4; } } else { for(intx=0; x<g_screen_width; ++x) { result_sse[x] =g_zero4; } } } void ResizeOptimized_sse() { if (g_source) { float sx = ox + (g_image_width/zoom)/2 + 0.5f; float sy = oy + (g_image_height/zoom)/2 + 0.5f; const __m128 c0_sse= _mm_set_ps( 1.0f/6.0f,4.0f/6.0f,1.0f/6.0f,0.0f/6.0f); const __m128 c1_sse= _mm_set_ps(-3.0f/6.0f,0.0f/6.0f,3.0f/6.0f,0.0f/6.0f); const __m128 c2_sse= _mm_set_ps( 3.0f/6.0f,-6.0f/6.0f,3.0f/6.0f,0.0f/6.0f); const __m128 c3_sse= _mm_set_ps(-1.0f/6.0f,3.0f/6.0f,-3.0f/6.0f,1.0f/6.0f); int u[g_screen_width]; int x_loop[9] = { 0, 0, 0, 0, g_screen_width, g_screen_width, g_screen_width, g_screen_width, g_screen_width }; __m128 wx_sse[g_screen_width][4]; for(intx=0; x<g_screen_width; ++x) { float fu = (x+sx) * zoom - 0.5f; u[x] = (int)floor( fu ); //u[x]-1>=0; if(u[x]+2 <0) x_loop[0] = x+1; if(u[x]+1 <0) x_loop[1] = x+1; if(u[x]+0 <0) x_loop[2] = x+1; if(u[x]-1 <0) x_loop[3] = x+1; //u[x]+2 <g_image_width; if(u[x]+2 <g_image_width) x_loop[4] = x+1; if(u[x]+1 <g_image_width) x_loop[5] = x+1; if(u[x]+0 <g_image_width) x_loop[6] = x+1; if(u[x]-1 <g_image_width) x_loop[7] = x+1; __m128 t1_sse = _mm_set1_ps(fu - u[x]); __m128 temp = c3_sse*t1_sse*t1_sse*t1_sse+ c2_sse*t1_sse*t1_sse+ c1_sse*t1_sse+ c0_sse; wx_sse[x][0]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(3,3,3,3)); wx_sse[x][1]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(2,2,2,2)); wx_sse[x][2]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(1,1,1,1)); wx_sse[x][3]=_mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0,0,0,0)); } __m128* image_sse= g_image_data; __m128* temp_sse[4] = { g_temp_data + g_screen_width * 0, g_temp_data + g_screen_width * 1, g_temp_data + g_screen_width * 2, g_temp_data + g_screen_width * 3 }; __m128* swap_sse[3]; int v_last = INT_MIN; for(inty=0; y<g_screen_height; ++y) { float fv = (y+sy) * zoom - 0.5f; int v = (int)floor( fv ); switch(v - v_last) { case0: break; case1: swap_sse[0]=temp_sse[0]; temp_sse[0] = temp_sse[1]; temp_sse[1] = temp_sse[2]; temp_sse[2] = temp_sse[3]; temp_sse[3] = swap_sse[0]; px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop); break; case2: swap_sse[0]=temp_sse[0]; swap_sse[1]=temp_sse[1]; temp_sse[0] = temp_sse[2]; temp_sse[1] = temp_sse[3]; temp_sse[2] = swap_sse[0]; temp_sse[3] = swap_sse[1]; px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop); px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop); break; case3: swap_sse[0]=temp_sse[0]; swap_sse[1]=temp_sse[1]; swap_sse[2]=temp_sse[2]; temp_sse[0] = temp_sse[3]; temp_sse[1] = swap_sse[0]; temp_sse[2] = swap_sse[1]; temp_sse[3] = swap_sse[2]; px_sse(v+ 0, g_source, image_sse,temp_sse[1], u, wx_sse,x_loop); px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop); px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop); break; default: px_sse(v- 1, g_source, image_sse,temp_sse[0], u, wx_sse,x_loop); px_sse(v+ 0, g_source, image_sse,temp_sse[1], u, wx_sse,x_loop); px_sse(v+ 1, g_source, image_sse,temp_sse[2], u, wx_sse,x_loop); px_sse(v+ 2, g_source, image_sse,temp_sse[3], u, wx_sse,x_loop); break; } v_last = v; __m128 t1_sse = _mm_set1_ps(fv - v); __m128 temp = c3_sse*t1_sse*t1_sse*t1_sse+ c2_sse*t1_sse*t1_sse+ c1_sse*t1_sse+ c0_sse; __m128 wy0 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(3,3,3,3)); __m128 wy1 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(2,2,2,2)); __m128 wy2 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(1,1,1,1)); __m128 wy3 = _mm_shuffle_ps(temp, temp, _MM_SHUFFLE(0,0,0,0)); __m128*src0 = temp_sse[0]; __m128*src1 = temp_sse[1]; __m128*src2 = temp_sse[2]; __m128*src3 = temp_sse[3]; __m128i* dest = (__m128i*)(g_screen+g_screen_width*y); BGRA*destTemp = g_screen + g_screen_width*y; for(intx=0; x<g_screen_width/4; ++x) { int prefetch = 8; _mm_prefetch((char*)(src0+prefetch),_MM_HINT_NTA); _mm_prefetch((char*)(src1+prefetch),_MM_HINT_NTA); _mm_prefetch((char*)(src2+prefetch),_MM_HINT_NTA); _mm_prefetch((char*)(src3+prefetch),_MM_HINT_NTA); _mm_stream_si128( dest++, _mm_packus_epi16( _mm_packs_epi32( _mm_cvtps_epi32(_mm_load_ps((const float*)(src0+0))*wy0 +_mm_load_ps((const float*)(src1+0))*wy1 +_mm_load_ps((const float*)(src2+0))*wy2 + _mm_load_ps((const float*)(src3+0))*wy3), _mm_cvtps_epi32(_mm_load_ps((const float*)(src0+1))*wy0 +_mm_load_ps((const float*)(src1+1))*wy1 +_mm_load_ps((const float*)(src2+1))*wy2 + _mm_load_ps((const float*)(src3+1))*wy3) ), _mm_packs_epi32( _mm_cvtps_epi32(_mm_load_ps((const float*)(src0+2))*wy0 +_mm_load_ps((const float*)(src1+2))*wy1 +_mm_load_ps((const float*)(src2+2))*wy2 + _mm_load_ps((const float*)(src3+2))*wy3), _mm_cvtps_epi32(_mm_load_ps((const float*)(src0+3))*wy0 +_mm_load_ps((const float*)(src1+3))*wy1 +_mm_load_ps((const float*)(src2+3))*wy2 + _mm_load_ps((const float*)(src3+3))*wy3) ) ) ); src0 +=4; src1 +=4; src2 +=4; src3 +=4; } } } }
  • 31. 최적화된 GPU 코드 // HLSL SSE Optimized 루프 groupshared float4 buffer2[g_thread_height + 4][g_thread_width]; float4 pxy_opt2(int v, int thread_x, int thread_y, float dv) { return w0(dv) * buffer2[v][thread_x] + w1(dv) * buffer2[v + 1][thread_x] + w2(dv) * buffer2[v + 2][thread_x] + w3(dv) * buffer2[v + 3][thread_x]; } [numthreads(g_thread_width, g_thread_height, 1)] float4 main(uint2 group : : SV_GroupID, uint2 thread : SV_GroupThreadID, uint2 dispatch : SV_DispatchThreadID) { float fu = (dispatch.x + sx) * zoom; float fv = (dispatch.y + sy) * zoom; int u = (int)floor(fu); int v = (int)floor(fv); float du = fu - u; float dv = fv - v; int v_first = (int)floor((group.y * g_thread_height + sy) * zoom); int v_last = (int)floor((group.y * g_thread_height + g_thread_height - 1 + sy) * zoom); int buffer_y = thread_y; for (int pv = v_first - 1 + thread_y; pv <= v_last + 3; pv += g_thread_height) { buffer2[buffer_y][thread.x] = px(u, pv, du); buffer_y += g_thread_height; } GroupMemoryBarrierWithGroupSync(); return pxy_opt2(v - v_first, thread.x, thread.y, dv); }
  • 32. Bicubic Test 벤치마크 0 20 40 60 80 100 120 140 CPU BruteForce CPU Optimized CPU SSE Optimiezed Intel HD 4000 nVIDIA GTS 440 nVIDIA GTX 680 Bicubic Test Benchmark 1배 확대 2배 확대
  • 33. CPU BruteForce 대비 결과 0 50 100 150 200 250 300 350 CPU BruteForce CPU Optimized CPU SSE Optimiezed Intel HD 4000 nVIDIA GTS 440 nVIDIA GTX 680 Benchmark vs CPU BruteForce 1배 확대 2배 확대
  • 34. Tiny Viewer 예제 • VTFetch 가 아닌 실제 캐릭터 연산을 GPU로 대체 – 시간이 일정하지 않은 키프레임 데이터 – 여러 애니메이션 블렌딩 가능 (CPU 와 동일) – 압축된 Key Frame, 이나 IK 구현도 가능 (CPU와 동일) • 계산 결과는 CPU와 동일한로 렌더러 사용 – CPU + GPU 동시에 연산을 분산해서 계산 가능 – GPU 연산 결과를 CPU 에서 다시 사용 가능
  • 35. 키프레임 업데이트 애니메이션 for each instance: for each animation track: find keyframe and interpolation local[i] = matrix(key.pos, key.rot) for each bone hierarchy: find parent for bone world[i] = local[i] * world[parent] for each target skin bone: find bone index from skin index skin[i] = invWorld[i] * world[bone]
  • 37. Local -> World 변환 루프 문제 World 0 World 1 World 2 World 3 World 4 World 5 World 6
  • 38. Group Sync 사용하기 instance = threadId /animationCount animation = threadId % animationCount instance = threadId /columnWidth column = threadId % columnWidth instance = threadId /skinCount column = threadId % skinCount GroupSync GroupSync GroupSync GroupSync GroupSync GroupSync
  • 39. Matrix -> Pos/Rot 메모리 줄이기 • CPU 도 GPU도 BandWidth 에 민감하다. – 최적화를 많이할수록 메모리 퍼포먼스가 중요 – 4x4 를 4x3 또는 tanslation, rotation 페어로 변경 • AoS 와 SoA 를 잘 골라쓰자. – SoA 가 더 빠르다고 하지만 다수의 스트림을 다루를때는 AoS 가 더 빠를때도 있다. – BandWidth/Bank 가 적을수록 AoS 를 선호한다.
  • 40. Tiny Viwer 벤치마크 Instace 개수 1024 2048 4096 8192 16384 Intel HD 4000 Full Render 13.29 25.74 50.23 99.24 Full CPU Copy 15.72 30.27 59.26 117.05 Full CPU Animation + Copy 15.73 30.23 59.27 117.12 Full GPU Animation 14.19 27.44 53.51 105.83 1/10 Render 1.99 4.26 6.67 14.73 27.88 1/10 CPU Copy 4.38 9.41 16.13 31.25 60.69 1/10 CPU Animation + Copy 5.15 8.37 16.15 36.35 60.93 1/10 GPU Animation 3.62 5.45 11.74 21.96 40.74 1 Indices Render 0.47 1.49 0.52 2.06 1.28 1 Indices CPU Copy 3.44 5.16 9.77 19.28 37.16 1 Indices CPU Animation + Copy 5.09 6.80 18.24 24.65 49.86 1 Indices GPU Animation 1.37 2.26 4.76 7.45 14.32 nVIDIA GTS 440 Full Render 3.80 7.25 14.04 27.44 54.21 Full CPU Copy 4.01 7.63 14.80 29.07 57.45 Full CPU Animation + Copy 4.01 7.63 14.78 28.92 57.07 Full GPU Animation 4.59 8.69 16.82 32.90 65.01 nVIDIA GTX 680 Full Render 0.58 1.10 2.16 4.25 8.39 Full CPU Copy 0.76 1.47 2.87 5.70 11.39 Full CPU Animation + Copy 2.42 4.50 8.67 17.11 33.38 Full GPU Animation 0.74 1.41 2.78 5.39 10.67
  • 41. 0.00 10.00 20.00 30.00 40.00 50.00 60.00 1 2 3 4 5 Intel HD Graphics 4000 + 1 Indices 1 Indices Render 1 Indices CPU Copy 1 Indices CPU Animation + Copy 1 Indices GPU Animation
  • 42. 0.00 10.00 20.00 30.00 40.00 50.00 60.00 70.00 1 2 3 4 5 nVIDIA GTS 440 / nVIDIA GTX 680 nVIDIA GTS 440 Full Render nVIDIA GTS 440 Full CPU Copy nVIDIA GTS 440 Full CPU Animation + Copy nVIDIA GTS 440 Full GPU Animation nVIDIA GTX 680 Full Render nVIDIA GTX 680 Full CPU Copy nVIDIA GTX 680 Full CPU Animation + Copy nVIDIA GTX 680 Full GPU Animation
  • 43. GPU 와 CPU 의 비동기 처리 CPU Animation IdleRenderCopyIdleRender IdleRenderCopy Copy CPU Animation Copy CPU Animation CopyCopy Render Idle Cmd GPU Animation Render GPU Animation Render GPU Animation Render Idle Cmd Idle Cmd Idle CPU Animation GPU Animation
  • 44. Tiny Viewer 실행 결론 • Intel U3317 + HD 4000 – CPU Copy 조차 GPU Animation 보다 느리다. – CPU Animation 까지 하면 부하가 늘어나서 더 느려진다. • Intel Sandy Bridge 2500 + nVIDIA GTS 440 – GPU 애니메이션 이 CPU Copy 시간에 비해 3.75 배정도 더 걸린다. – GPU Animation 손해가 제일 큰 경우이다. CPU 부하를 나을 수 있다. • Intel Sandy Bridge 2500 + nVIDIA GTX 680 – CPU Copy 시간과 GPU Animtion 시간이 비슷하다. – GPU 가 CPU Animtion 결과를 기다리므로 GPU Animtion 이 항상 빠르다.
  • 45. GPU 의 약점을 알아두자 • 까다로운 GPU 메모리 접근 – 대역폭은 넓지만 랜덤 접근에 강하지 않다. – Shared Memory 도 최악의 경우 16배까지 느려진다. • 생각보다 느린 Thread 당 연산 속도 – 클럭이 CPU의 1/4~1/5 정도, IPC도 1/5~1/8 – OOE 는 지원하지만 CPU만큼 고급은 아니다. • CPU와 데이터와 결과를 주고 받아야한다. – 복사 비용은 매우매우매우 비싸다. 최대한 재사용하자. – 주고 받는 데이터 크기도 최소화 하자. (fp16, uint16)
  • 46. GPU 공부하면서 어려웠던 점들 • 아직 제대로된 프로파일러가 없다 – 미묘한 메모리 패널티 – 미묘한 Occupancy 패널티 • GPU 마다 특성들이 다르다 – 누구는 AoS 가 좋고 누구는 SoA 가 좋대 – 누구는 벡터를 쓰면 좋고 누구는 그냥하는게 좋대 • Intel 내장 그래픽 카드의 버그 – 가끔 데이터를 못읽어와서 고생했다. – nVIDIA는 잘돌아가니 거기서 해보자.
  • 47. Q&A ?

Hinweis der Redaktion

  1. BruteForce 100.22ms vs Optimized 21.42 ms X1 확대시 21.42 ms X2 확대시 15.16 ms Optimized 21.42 ms vs SSE Optimized 5.28 X1 확대시 5.28 ms X2 확대시 3.43 ms
  2. BruteForce X1 확대시 2.55 ms X2 확대시 1.96 ms 최적화 X1 확대시 1.43ms X2 확대시 1.34ms