GPGPU(CUDA)를 이용한 MMOG 캐릭터 충돌처리

4,432 views
4,059 views

Published on

NDC 2012 발표자료
GPGPU(CUDA)를 이용한 MMOG캐릭터 충돌처리

Published in: Technology
0 Comments
16 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,432
On SlideShare
0
From Embeds
0
Number of Embeds
193
Actions
Shares
0
Downloads
77
Comments
0
Likes
16
Embeds 0
No embeds

No notes for slide

GPGPU(CUDA)를 이용한 MMOG 캐릭터 충돌처리

  1. 1. GPGPU(CUDA)를 이용한 MMOG 캐릭터 충돌처리유영천http://yuchi.netTwitter: @dgtman
  2. 2. 목표• GPU기반 병렬 프로그래밍의 소개• GPGPU기술 습득의 동기부여• 자세한 프로그래밍 기법은 생략한다.
  3. 3. 어따 쓸까…http://youtu.be/3sl63XhuuWc MIKU 데모(타원체)
  4. 4. 서버에서 충돌처리를 하면 좋은 점• 위치,속도 Hack의 원천 봉쇄• 서버측에서 능동적으로 이것저것 하기 좋음• 정밀한 위치 추척 가능(30,60프레임 단위로 처리 가능)• 클라이언트에서 플레이어 제어하듯 서버에서도 플레이어,NPC,몬스터의 이동을 프로그래밍 가능.
  5. 5. 걸림돌• 동기화 맞추는게 까다롭다.• 서버측 CPU부하가 심하다.
  6. 6. 동기화의 문제• 여기서 다룰 주제는 아닙니다• 그냥 열심히 하면 됩니다(될겁니다?)
  7. 7. CPU부하의 문제• 요새 CPU는 기본 4Core• 충돌처리 코드를 멀티스레드로 작동하도록 만든다
  8. 8. 그래도 감당이 안되면(1)?• CPU를 더 꽂는다.• 일반 데스크탑 CPU는 멀티 CPU구성 불가• 2개 이상 꽂을 수 있는 Xeon CPU를 사용한다.참고) 프로젝트 엡실론에선 플레이어 4000명을 커버하기 위해 6core Xeon 2개를 사용했음.
  9. 9. 그래도 감당이 안되면(2)?• 4개 이상 꽂을 수 있는 Xeon CPU를 사용한다• 여기서부턴 조립불가. 가격이 미친듯이 뛰기 시작함• 10 Core Xeon을 8개 꽂으면 확실! Intel® Xeon® Processor E7-8870 4616$ x 8 = 36928$(CPU만) 서버머신 가격은 더 비쌈.
  10. 10. 저렴하게 갑시다.• CPU가 계산할 충돌처리 코드를 GPU에서 돌린다.
  11. 11. GPGPU(General-Purpose computing on GPU• GPU를 사용하여 CPU가 전통적으로 취급했던 응용 프로그램들의 계산을 수행하는 기술• GPU 코어 1개의 효율은 CPU 코어 1개에 비해 많이 떨어지지만 코어의 개수가 엄청나게 많다.• 많은 수의 코어를 사용하면 병렬 산술 연산의 성능은 CPU의 성능을 압도한다.
  12. 12. GPU vs CPUGM < ν Gundam - 택도없는 승부. 이것은 학살.
  13. 13. GPU vs CPUGM <= ν Gundam – 그래도 혹시 모르지 않나?
  14. 14. GPU vs CPUGM > ν Gundam – 한판 붙자!!!
  15. 15. GPU vs CPUGM > ν Gundam – 줄 서서 1:1로 붙으면 자살행위
  16. 16. GPGPU의 특징강점 약점• 엄청난 수의 스레드를 사용할 • 흐름제어 기능 자체가 수 있다. 빈약하다.• 부동소수점 연산이 엄청 • 프로그래밍 기법의 빠르다. 제약(재귀호출불가)• 컨텍스트 스위칭이 엄청 • Core당 클럭이 CPU에 비해 빠르다. 많이 느리다(1/3 – 1/2수준)• 그래서 충분히 병렬화된 산술처리에는 짱.
  17. 17. CUDA (Compute Unified Device Architecture)• C언어 등 산업 표준 언어를 사용하여 GPU에서 작동하는 병렬처리 코드를 작성할 수 있도록 하는 GPGPU기술• nVidia가 개발,배포.그래서 nVidia GPU만 가능.• 비슷한 기술로 OpenCL, Direct Compute Shader가 있음.
  18. 18. From nVidia CUDA_C Programming Guide
  19. 19. GPU구조의이해(GF110)*CPU의 Core에해당하는 것은 GPU의SM이다.따라서 흐름제어의측면으로 보면 512 CoreCPU가 아니라 16 CoreCPU에 상응한다.
  20. 20. Thread• CPU의 Thread와 비슷…하다(같지 않다).• GPU의 Core(혹은 SP)에 맵핑• 독립적인 컨텍스트를 가지고 있다(라고 해봐야 연산에 필요한 컨텍스트 뿐).• 컨텍스트 스위칭이 겁나 빠르다.• 독립적인 흐름제어는 불가능하다(32개 단위로 묶여서 움직임).
  21. 21. Block• Block -> N개의 Thread집합. SM에 맵핑• 동일 Block의 스레드는 L1캐쉬와 Shared Memory를 공유한다.• 스레드만 잔뜩 있으면 될것 같은데 왜 이런게 있냐면…그것은 아마도 CPU의 core !≒ GPU의 core CPU의 core ≒ GPU의 SM 이기 때문일 것이다.
  22. 22. Grid• Block들의 집합• 그냥 쉽게 생각해서 그래픽 카드 -> Grid
  23. 23. Thread, Block,Grid• Thread -> Core• Block -> SM• Grid -> GPU
  24. 24. Kernel• GPU에서 돌아가는 C함수• CPU멀티스레드 프로그래밍에서의 Thread Function과 같다.• 복수의 Thread에 의해 실행된다.• CPU측(host)에서 호출되며 GPU측(device)코드를 호출할 수 있다.
  25. 25. N x N 매트릭스의 합을 구하는 CUDA커널함수
  26. 26. CUDA메모리• Global Memory – 보통 말하는 Video Memory. 크다. 느리다.• Register – CPU의 레지스터와 같다. 로컬변수, 인덱싱하지 않고 크기가 작은 로컬배열은 레지스터에 맵핑된다. 작다. 빠르다.• Shared Memory – Block의 스레드들이 공유하는 메모리. 블럭당 48KB. 작다. 빠르다.참고) SM에 하나씩 존재하는 64KB의 캐쉬를 16KB L1캐쉬와 48KB Shared Memory로 사용
  27. 27. 메모리 억세스 범위• Thread -> Local Memory , Shared Memory , Global Memory• Block -> Shared Memory, Global Memory• Grid -> Global Memory
  28. 28. CUDA 이미지 프로세싱 예제
  29. 29. 성능 비교2885x4102크기의 이미지에 5x5 GaussianBlur를 3회 적용1Threads - Intel i7 2600K @3.8GHz-> 10054.3ms8Threads - Intel Intel i7 2600K @3.7GHz-> 2247.8msCUDA - GTX480 (15 SM x 32 Core = 480Core)->203.7ms참고) 이미지 필터링 코드는 CUDA, CPU 완전 동일. 알고리즘상의 최적화는 없음. 병렬처리 효율의 비교를 위한 테스트임.
  30. 30. 단순포팅예제
  31. 31. CUDA로 충돌처리하기
  32. 32. 구현전략• 싱글스레드 C코드로 작성한다• 자료구조상으로 최적화한다.• 멀티스레드로 바꾼다.• CUDA로 (단순) 포팅한다.• 퍼포먼스를 측정한다.• 절망한다.• CUDA에 적합하도록 일부의 설계를 변경 한다.• 최적화한다.
  33. 33. 충돌처리코드개요• 움직이는 타원체의 궤적에 걸칠만한 삼각형과 다른 타원체를 수집• 루프를 돌며 각각의 삼각형과 타원체에 충돌체크• 가장 가까운 충돌점을 찾는다• 반사, 또는 미끄러짐 벡터를 구한다.• 충돌 후 벡터의 스칼라량이 0에 근접하면 탈출• 그렇지 않으면 다시 1부터 반복
  34. 34. CPU코드로 작성• CUDA로 포팅할 것을 염두해 두고 작성한다(재귀호출 사용불가)• 주변 삼각형과 타원체를 골라내기 위해 공간분할이 중요하다. (BSP Tree, KD Tree, Grid등) BSPTree나 KDTree는 CUDA로 포팅하기도 어렵고 CUDA로 돌리려면 성능이 많이 떨어진다. GRID와 같이 단순한 자료구조를 권장.
  35. 35. CPU코드로 작성• 일단 CPU상에서 멀쩡히 돌아가는게 가장 중요하다.• SSE,AVX등 SIMD최적화는 나중에 생각하자.CUDA포팅에 걸림돌이 된다.
  36. 36. Multi-Thread코드 작성• 잘 돌아가는 Single-Thread코드를 작성했으면 Multi- Thread버젼을 만든다.• 충돌처리할 오브젝트들을 Thread개수로 나눠서 처리.
  37. 37. Multi-Thread코드 작성(Tip)• 4000obj / 8 thread = 500, 1 thread당 500개처리 -> OK? ->NO!!!! 오브젝트마다 처리시간이 다르기 때문에 탱자탱자 노는 thread가 생긴다.• Thread들이 경쟁적으로 큐에서 충돌처리할 오브젝트를 가져간다. -> OK!!! 거의 노는 thread 없이 CPU자원을 꽉 채워서 사용할 수 있다.
  38. 38. CUDA코드로 포팅• 표준적인 문법만 사용했다면 별로 수정할게 없다.• Multi-Thread코드와 유사하다. CPU Thread -> CUDA Thread로 바로 맵핑하면 된다.
  39. 39. 단순포팅• 처리하고 하는 [원소 1개 > thread 1개] 직접 맵핑• 1 pixel -> 1 thread, 1 오브젝트-> 1스레드• C로 짠 코드는 거의 100% 그대로 돌릴 수 있다.• 이미지 프로세싱 매트릭스 연산 등에선 충분히 효과가 있다.
  40. 40. 단순포팅의 결과• 충돌처리에선 CPU코드(멀티스레드)보다 느리게 작동한다!• HW스펙만으로 동시 처리 가능한 오브젝트 수를 짐작해보면… CPU(i7 2600) : 4Core x 2(HT) = 8 Thread, CUDA(GF110) : 32Core x 16SM = 512 Thread단순하게 생각하면 동시에 처리하는 오브젝트 수가 훨씬많은데 왜 느릴까.
  41. 41. SIMT(Single Instruction Multiple Threads)• 동일 명령어를 여러개의 Thread가 동시 실행• GPU는 기본적으로 SIMT로 작동• N차원 벡터를 다루는 산술처리에선 적합• Thread중 일부가 분기를 하거나 루프를 하면 나머지 Thread들은 대기 -> 병렬처리의 의미가 없어짐.
  42. 42. WARP의 이해• Thread들을 묶어서 스케쥴링하는 단위• 현재까지는 모든 nVidia GPU의 1 Warp는 32 Thread. 즉 32 Thread는 늘 같은 같은 코드 어드레스를 지나감.• 동시 수행 가능한 Warp 수는 SM의 Warp 스케쥴러 개수에 의존. SM당 2개의 Warp를 동시에 실행가능(GF100)
  43. 43. WARP의 이해• 32 Thread중 1개의 Thread만이 분기해서 Loop를 돌고 있으면 31개의 Thread는 1개의 Thread가 Loop를 마치고 같은 코드 어드레스를 수행할 수 있을 때가지 대기.• 각 Thread가 다른 루프, 다른 흐름을 타는 것은 엄청난 성능 저하를 부른다.
  44. 44. Worst CaseDWORD ThreadIndexInBlock = threadIdx.y * blockDim.x + threadIdx.x;DWORD UniqueThreadIndex = UniqueBlockIndex * ThreadNumPerBlock + ThreadIndexInBlock;int CountList[32] = {0,1,2,3,...,31};// 32개의 스레드가 동시에 루프를 돈다.for (int i=0; i<CountList[UniqueThreadIndex]; i++){ ...}// CountList[UniqueThreadIndex] == 0 인 스레드도 CountList[UniqueThreadIndex] == 31인 스레드를대기// 모든 스레드가 가장 오래 걸리는 스레드에 맞춰서 움직인다.즉// 모든 스레드가 31번 루프를 도는 것과 같다.
  45. 45. SIMT를 고려한 설계• 가장 시간을 많이 잡아먹는 코드는 어디인가?• 병렬화 하기 좋은 코드는 어디인가?• 가장 시간을 많이 잡아먹고 병렬화 가능한 코드를 병렬화한다.
  46. 46. 충돌처리 코드에서 SIMT를 고려한 설계• 충돌처리의 경우 Loop를 돌며 인접한 삼각형과 오브젝트(타원체)를 찾는 코드에서 가장 많은 시간을 잡아먹는다.• WARP단위로 명령어를 실행하기 때문에 삼각형과 타원체를 찾는 루프에서 대부분의 코어가 논다.• [1개의 Thread가 N번 Loop -> M개의 Thread가 N/M번의 Loop] 로 수정
  47. 47. 충돌처리 단순포팅에서의 병목Thread Thread ThreadObject Object Object
  48. 48. 루프개선 (단순화된 모델에서의 루프 개선)• 충돌처리 코드에서 가장 병목이 걸리는 루프를 단순화 시켜보자. A. 오브젝트 한 개당 N개의 타원체와 삼각형에 대해 충돌체크를 하고 가장 가까운 충돌점을 찾는다.B. 오브젝트 한개당 N개의 숫자를 들고 있고 그 중 가장 작은 수, 혹은 가장 큰 수를 찾는다.
  49. 49. 루프개선 (단순화된 모델에서의 루프 개선)• 8192개의 CELL이 있다.• 각 CELL은 랜덤하게 1 ~ 8192범위의 숫자를 1 ~ 8192 개 가지고 있다.• 각 CELL에 대해서 최대값을 구한다. 1) CPU Single Thread 2) CUDA , 1 Cell -> 1 Thread 3) CUDA , 1 Cell -> 1 Block
  50. 50. 1 1 1 1 2 2 2 2 3 3 3 3 . . . . . . . .8191 99 9 498192 100 10 508192 100 10 50
  51. 51. 1 Obj -> 1 ThreadThread Thread Thread Thread1 1 1 12 2 23 3 34 45 5677 5 3 1
  52. 52. 1 Obj -> 1 Block Block Block Block BlockThread 1 Thread 1 Thread 1 Thread 1Thread 2 Thread 2 Thread 2Thread 3 Thread 3 Thread 3 1Thread 4 Thread 4 3 Thread 1Thread 5 Thread 5 Thread 2Thread 6 5 Thread 1 Thread 3Thread 7 Thread 2 Thread 4 Thread 1 Thread 3 Thread 5 7 Thread 2 Thread 4 Thread 5 5 Thread 3 Thread 6
  53. 53. 1 Cell -> 1 Thread
  54. 54. 1 Cell -> 1 Block
  55. 55. 1 Cell -> 1 Block
  56. 56. 단순화된 모델에서의 Loop 개선 결과• CPU Single-Thread 35.68ms• CUDA ,1 Cell - 1 Thread 11.63ms• CUDA , 1 Cell - 1 Block 1.24ms
  57. 57. • 같은 방식으로 CUDA충돌처리 코드에 적용
  58. 58. 1 Object -> 1 ThreadThread Thread ThreadObject Object Object
  59. 59. 1 Object -> 1 Block Block BlockObject Thread Object Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Block Object Thread Thread Thread
  60. 60. 충돌처리 루프 수정 후• 빨라졌다!• 그러나 생각보다는 많이 안빠르다.(CPU대비 2-3배 성능)
  61. 61. Occupancy• SM당 활성화 Warp 수 / SM당 최대 Warp 수• GPU의 처리능력을 얼마나 사용하는지 척도• 대부분의 경우 Occupancy가 증가하면 효율(시간당 처리량)도 증가함• Global Memory 억세스로 latency가 발생할때 유휴 Warp를 수행함.• GPU 디바이스의 스펙에 상관없이 WARP를 많이 유지할 수 있으면 처리성능이 높아진다.
  62. 62. Occupancy Calculator
  63. 63. Occupancy늘리기• 많은 워프를 사용하여 Latency 숨기기• 레지스터 수를 줄이도록 노력한다.• Shared Memory사용을 아낀다.(줄이는 것이 아니다)• Shared Memory사용량 때문에 Occupancy를 떨어뜨린다면 Shared Memory대신 Global Memory를 사용하는 것이 나을수 있다.(L2캐쉬 때문에 생각보단 느리지 않다)
  64. 64. Shared Memory아껴쓰기• 자주 억세스할 경우 Shared Memory에 올려놓으면 빠르다.• 무작정 마구 쓰면 SM에 맵핑할 수 있는 블럭 수가 크게 떨어진다.• Shared Memory절약 ->Active Block수 증가 -> 성능향상
  65. 65. Register 사용량 줄이기• 사실 뾰족한 방법이 있는건 아니다.• 알고리즘 수준에서의 최적화 -> 결과적으로 작은 코드.• 레지스터 개수를 컴파일 타임에 제한할 수는 있다(경험상 별로 안빨라짐).
  66. 66. 추가적인 최적화• Unrolling• PCI-E 버스 병목 줄이기• CDUA Stream이용(이번에는 안다룸)
  67. 67. Unrolling• 루프를 풀면 컨트롤 명령어 수를 줄일 수 있다. 산술 명령어를 더 실행할 수 있다.• Occupancy증가만큼은 아니지만 꽤 효과가 있다.• 아직까지는 nvcc가 능동적으로 Unrolling을 해주지는 못하는 것 같다. 수동으로 해주면 효과가 있다.
  68. 68. PCI-E버스 병목 줄이기• 한번에 몰아서 전송한다.• 퍼포먼스를 떨어뜨리지 않는 선에 압축할 수 있으면 압축한다. 이 경우 커널 호출을 여러번 해야한다.• 처리량의 향상에는 큰 도움을 주지 않지만 응답성 면에선 차이가 크다(게임에선 처리량보다 응답성이 중요).
  69. 69. 충돌처리 성능 테스트• 테스트 조건  타원체 7657개(최대 8192개)  삼각형 77760개  타원체 VS 타원체, 타원체 VS 삼각형  유휴시간 없이 최대프레임으로 충돌처리를 수행. 한번 충돌처리에 걸리는 시간 측정.
  70. 70. CPU i7 2600K (8Threads @3.7GHz) – 326msCUDA GTX480 – 40ms (CPU대비 8.15배)
  71. 71. 게임 클라이언트에서 사용• 하나의 GPU로 렌더링과 CUDA처리를 겸하게 되면 퍼포먼스 하락.• PCI-E버스 병목도 원인.• GPU가 두개 꽂힌 시스템이라면 게임 클라이언트에서도 프레임향상에 도움이 된다.• 두번째 GPU로 충돌처리를 하면 응답시간이 더 빠르다. (캐릭터 100마리일 때 약 3~4배 차이)
  72. 72. CUDA적용의 장점• 가격 대비 성능이 매우 뛰어나다.• CPU자원의 여유를 확보할 수 있다..• 같은 기능의 코드를 CPU와 GPU 양쪽에서 동시 처리해도 좋다. 성능 2x이상 향상.• 확장성에는 큰 도움이 된다.• 새로운 GPU가 나올때마다 성능이 크게 향상된다.• 기술 덕후의 호기심을 충족시킨다.
  73. 73. CUDA적용의 단점or걸림돌• 단순 포팅은 의미가 없으므로 코드를 상당 부분 수정해야한다.• GPU에 맞춰서 최적화 하고나면 코드가 아스트랄해진다. 유지보수가 어렵다.• 디버깅이 지랄같다. (희소식! Parallel Nsight 2.2에서 드디어 싱글 GPU 디버깅이 가능해졌다!!!)
  74. 74. 결론• 게임서버 용도이고 돈이 많다면 굳이 CUDA 안써도 됨.
  75. 75. 하지만 이걸 보면 쓰고 싶을걸?32core 이상의 Xeon 서버 Dell Power Edge 910 (8 core x 4) - 27989$ HP ProLiant DL980 (10 core * 4) 55229$Tesla 장비(PCI-E 카드) Tesla m2070(16 SM , 512 core) - 2699$GeForce 그래픽카드 GTX580 (16 SM , 512 core) - 400$ ~ 500$Tesla내장 서버 IBM HS23 m2070 Server - 6999$
  76. 76. 참고문헌• Programming Massively Parallel Processors (David B.Kirk , Wen-mei W. Hwu)• CUDA 병렬프로그래밍 (정영훈)• nVidia GPU Computing SDK 4.1 Documents
  77. 77. Q/A

×