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줄 이내에 초기화 코드 작성, 전체적으로 적은 코드량
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
– 쓰레드간에 공유하는 작은 크기의 메모리
– 쓰레드간 주고 받을 정보나 중복 데이터 저장
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 계산
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줄
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]
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
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는 잘돌아가니 거기서 해보자.