M2 클라이언트 스레딩 아키텍쳐넥슨 코리아 N스퀘어개발본부 1실 GTR팀전형규henjeon@nexon.co.kr2013.04.24
발표자 소개넥슨 10년차 프로그래머현재 GTR팀 팀장참여 프로젝트마비노기XBOX360 마비노기마비노기 2주요 관심사Computer GraphicsReal-Time Rendering
발표 내용M2 클라이언트의 멀티 스레딩 구조를 설명스레딩 사례 소개미들웨어 통합 가이드다루지 않는 것서버 스레딩멀티 스레드 API 사용법각종 잠금 장치 사용법락프리(Lock-Free) 알고리즘
M2 소개M2 엔진 이름사용 중인 미들웨어들
자주 등장하는 용어M2 : 마비노기2 클라이언트 프로그램스레드Thread , 싱글 스레드Single Thread, 멀티 스레드Multithread조인Join : 동기화리졸브Resolve : 커맨드 큐Command Que...
개요Overview
개발 방향매우 보수적이고 안전한 스레딩 모델을 택함싱글 스레드 구조로 시작한 오래된 프로젝트.프로젝트 시작 시 상용엔진을 사용하다가 제거하고 직접 제작했다.
구조 개요기능 수준 병렬화Functional Parallelization로직 -> 렌더 -> D3D의 파이프라인일부 작업들은 데이터 병렬화Data Parallelization애니메이션, 파티클, …두 개의 백그라운드 작...
압니다, Task Parallelism하면 좋지요… 그러나…경험 있는 프로그래머가 많이 필요.작업 분할Task Partitioning 은 어렵다.디버깅이 어렵다.물론 익숙해지면 쉽겠지만…미들웨어 통합은 어떻게?업계 표준...
두 가지가 없다메인 스레드가 없다.프로세스가 생성될 때 만들어진 기본 스레드라는 의미만 존재..전담Dedicated 스레드가 없다.대신, 스레드 타입이 있다: 로직, 애니메이션, 렌더, 백그라운드예외: 디바이스 스레드(...
스레드 관리Intel TBB(Threading Building Blocks) 사용.TBB가 아닌 OS 스레드를 사용하는 영역• 백그라운드 작업디스크 IO비동기 처리• 전담 스레드미들웨어 내부에서 생성하는 작업 스레드네트...
스레드 통신메시지(커맨드) 큐를 사용한 단방향 통신로직 -> 렌더 -> 디바이스Single Reader, Multiple Writer역방향 통신은 조인 구간에서 처리.로직과 디바이스 스레드는 서로 통신하지 않는다.디바이...
렌더링은 로직보다 1프레임 늦게 간다로직은 렌더링 객체에 직접 접근할 수 없다.프록시Proxy를 통해서 간접적으로 접근한다.조인 구간에서 직접 접근할 수 있다.로직에서 처리한 렌더링 작업은 그 다음 프레임에서 실행된다....
렌더 – 디바이스 스레드 통신두 가지 모드가 있다.싱글 버퍼링(기본값)더블 버퍼링싱글 버퍼링(푸시 버퍼Push Buffer) 모드잠시 버퍼링한 후에 디바이스 커맨드 버퍼로 전송.더블 버퍼링 모드커맨드를 쌓아둔 후에 조인...
두 가지 방식의 특징싱글 버퍼링은 약간 느리다.렌더 스레드에서 최초의 명령을 보내기까지 수 ms 걸린다.이 시간 동안 디바이스 스레드가 공회전한다.더블 버퍼링은 결과가 1프레임 지연된다.60fps 이상에서는 거의 느껴지...
런타임에 모드를 선택일명, 스마트 버퍼링60fps 이상에서는 더블 버퍼링한다.그 미만에서는 싱글 버퍼링.
자원의 스레드 안전성대부분의 자원은 특정 스레드에서만 사용되므로 안전하다.생성 후에 그 내용을 변경할 수 없다.내용을 변경하고 싶다면 사본을 생성한다.동시에 사용하는 자원은 불변Immutable 타입으로 제한.1/3
자원의 스레드 안전성 2/3생성된 스레드와 파괴된 스레드가 다를 수 있다.참조 카운터로 자원의 수명을 관리한다.D3D 자원이 좋은 예.특정 스레드에서 파괴되어야 하는 자원에 주의하자.
실행 중인 스레드가디바이스 스레드인지 조사자원의 스레드 안전성 3/3
세부 사항Implementation Details
Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveC...
작업의 스레드 타입ThreadType::SimulationThreadType::AnimationThreadType::RenderThreadType::Background모든 태스크는 다음 타입 중 하나 ‘이상’을 가진다....
스레드 어피니티AffinityWindows API를 호출하는 작업D3D를 사용하는 작업어떤 작업은 반드시 정해진 스레드에서 실행되어야 한다.다음 작업들은 디바이스 스레드에서 실행된다.Debug ConsoleProcess...
TBB와 스레드 타입디바이스, 백그라운드 작업은 직접 생성한 스레드에서 실행.좀 더 세밀한 제어가 필요하기 때문.나머지 작업들은 모두 TBB 스케줄러로 실행한다.
D3DDeviceValidation 디바이스 검사D3D 디바이스 로스트, 창 크기 변경 등의 이벤트를 처리한다.자주 발생하는 이벤트가 아니므로 싱글 스레드로 처리한다.
Debug ConsoleProcessing 디버그 콘솔 처리개발 전용 기능들을 처리한다.싱글 스레드로 실행한다.빠르고 쉽게 만들 수 있다.디버깅하기 쉽다.배포 버전에서 제거되므로 유저 경험에 영향을 주지 않는다.
입력장치 처리Input DevicesProcessingWindows API의 스레딩 문제를 피하기 위해서,싱글 스레드로 실행.수행 시간이 1ms 안쪽이므로 성능에 영향이 없다.
Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveC...
1/4로직Logic모든 게임 로직은 이 태스크에서 실행된다.게임 상태 갱신네트워킹AI사운드UI
2/4로직Logic렌더링 명령은 프록시 객체를 통해서 처리실제 객체는 렌더 스레드 안에 있다.프록시 객체는 큐에 명령을 쌓아두기만 한다.명령 실행은 에서.더블버퍼링된다: 조인 구간에서 버퍼 교체.RenderQueueRe...
3/4로직Logic프록시 객체는 단순히 메시지만 전달Delegation실제 수행전달
4/4로직Logic상태 질의state query는 금지.어쩔 수 없이 필요한 경우에는…조인 구간에서 동기화 하거나 프록시에 동일하게 구현
물리Physics로직 스레드 타입이다.ThreadType::Physics 이런 거 없다.오직 로직 스레드에서만 물리를 처리한다.렌더 스레드는 물리를 모른다.레이 캐스팅은 예외• raycastClosestShape() 등...
1/3애니메이션Animation캐릭터 단위로 병렬 처리한다.tbb::parallel_for()를 쓰면 간단하지만…작업 스레드 수를 제어하고 싶어서 간단하게 직접 구현.캐릭터 목록이 담긴 배열의 주소를각 태스크가 경쟁적으...
2/3애니메이션Animation캐릭터 사이의 의존성 분석이 필요서로 참조하고 있는 캐릭터들을 동시에 처리하면,race가 발생하거나 크래시될 수 있다.의존성 있는 캐릭터들은 하나의 태스크에서 순서대로 처리한다.
3/3Animation코드 예:애니메이션
Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveC...
로직 스레드에서 전송한 커맨드를 실행한다.렌더링 큐 리졸브RenderQueueResolve
코드 예:
컬링Cull일반적인 CPU 시야 컬링 View Frustum Culling 수행.GPU로 가려진 물체 컬링Occlusion Culling 수행Hierarchical Occlusion Map 기반.자세한 설명은 생략.커서...
파티클Particle애니메이션과 동일하게 병렬화했다.로직과 밀접한 관계가 있는 파티클 타입은 에서 처리한다.Logic예: 동전 효과
1/3렌더링Render장면 업데이트, 렌더링 파이프라인 실행.디바이스 커맨드 큐로 명령어들을 전송한다.코드 예:
2/3렌더링Render미룰 수 있는 작업은 최대한 디바이스 스레드로 미룬다.게을러서 병목이 아니라서 지켜보고 있는 중.병렬화할 여지가 많다
3/3렌더링RenderD3D 디바이스 자원에 직접 접근하고 싶을 때 콜백Callback을 사용.코드 예:
디바이스 큐 리졸브D3DQueueResolve렌더 스레드가 전송한 명령어들을 실행한다.실제로 D3D를 사용하는 구간이다.프레임 버퍼 크기 변경 처리윈도 크기 변경 등으로 프레임 버퍼 크기가 변경되면,명령어가 생성된 시점...
1/2Join 조인각 작업의 상태를 동기화하는 단계로,이 구간을 가급적 빨리 실행해야 성능에 유리하다.스레드에 안전한 구간각종 코드들의 축제가 벌어진다.현재 이 코드는 남아 있지 않다…
2/2Join 조인커맨드의 실행 순서에 주의!!스레드에 안전하다고 커맨드 큐 없이 바로 상태를 조작하면,실행 순서가 역전된다.이를 방지하기 위해서 조인 구간에서 프록시의 본체에 접근할 경우,커맨드 큐를 먼저 실행한다.•...
BackgroundWorker 백그라운드 작업비동기Asynchronous 처리, 파일 IO 작업.두 작업 스레드가 있다낮은 우선순위 작업• 리소스 로딩, 파일 IO• 이미지 업로드높은 우선순위 작업• 캐릭터 커스터마이징
정리DebugConsoleProcessingInputDevicesProcessingD3DQueue ResolveAnimationJoinLogic PhysicsRenderQueueResolve CullParticlePar...
디버깅Debugging Tips
스레드 타입 검사다른 스레드에서 실행되는 코드에 접근할 수 없도록 설계를 잘 해야 한다.그래도 어떻게든 용케 방법을 찾아서 호출하더라…작업 시작 시 자기 타입을 TLSThread Local Storage에 기록하고,주요...
스레드 프로파일링스레드 작업 막대 그래프1/2
스레드 프로파일링타이밍 그래프2/2
스레딩 옵션 변경최소한 시작 옵션으로 지정할 수 있어야 한다.런타임에 변경할 수 있으면 스레딩 성능 비교 및 디버깅에 유용.여러 개의 클라이언트를 실행할 경우, 싱글 스레드 모드가 편하다.
커맨드 생성 위치 추적커맨드가 생성되는 시점과 실행 시점이 다르다.커맨드 실행 코드에 브레이크 포인트를 걸어봤자 얻는 것이 별로 없다.커맨드를 추가하는 위치가 더 중요하다.커맨드에 ID를 붙이고, 특정 ID를 생성하는 ...
코드 예:
스레딩 사례 소개Case Study
렌더링 리소스 로딩필연적으로 비동기 작업이다.보통, 렌더 스레드가 리소스를 요청하지만,렌더 스레드는 디바이스 자원을 생성할 수 없다.여러 스레드가 참여하는 파이프라인 구조가 필요하다.텍스쳐 로딩 파이프라인 구현 사례 소개.
텍스쳐 로딩 과정 개요Texture DescriptorLoadingTextureRequested Cache Generation File LoadingDevice TextureCreationTextureLockingmem...
디스크립터 로딩Texture DescriptorLoading텍스쳐에 대한 정보가 담긴 작은 파일을 바로 로딩한다.텍스쳐 크기, 형식 등은 요청 즉시 반환한다.32*32 썸네일Thumbnail도 같이 로딩해서 로딩 완료 ...
캐시 파일 생성개발 클라이언트 전용 기능.백그라운드 스레드에서 실행한다.어셋 저장소Repository에는 무압축 원본 텍스쳐만 올리고,런타임에 텍스쳐를 가공한다.매번 변환하면 너무 느리므로 캐시를 만들어둔다.CacheG...
파일 로딩백그라운드 스레드에서 실행.File Loading텍스쳐 파일을 열고 원하는 밉맵을 메모리로 복사한다.
디바이스 텍스쳐 생성디바이스 스레드에서 실행.D3D 텍스쳐 객체를 생성한다.Device TextureCreation
텍스쳐 락 걸기디바이스 스레드에서 실행.텍스쳐에 락을 걸고 밉맵 복사를 준비한다.TextureLocking
이미지 복사백그라운드 스레드에서 실행.락을 걸고 받아 둔 주소에 파일 내용을 복사한다.memcpy()
텍스쳐 락 풀기디바이스 스레드에서 실행.언락한다. 텍스쳐를 사용할 준비를 거의 다 했다.TextureUnlocking
마무리조인 구간에서 실행.Finalization각종 자원 해제 및 뒷정리 단계.파이프라인이 중간에 취소되어도 이 단계는 무조건 실행된다.
렌더링 리소스 로딩 - 정리렌더링 리소스 로딩은 여러 스레드를 통과하는 복잡한 작업이다.스레드 어피니티를 고려하는 파이프라인 구현이 필요.찾아봐도 용도에 딱 맞는 구현이 없다.만들기 어렵지 않아서 다행이다.
종료 처리 1/6게임 루프 전환, 종료 시 커맨드 큐의 정리는 언제나 골치 아프다.히스토리 채널. Vikings. 2013대충 종료하면 발할라에 갈 수 없다.
종료 처리 2/6실행할 것이냐 그냥 버릴 것이냐쌓인 커맨드의 처리 문제그냥 버린다 / 바로 실행한다 / 큐에 넣는다종료 처리 중에 발생하는 커맨드커맨드 내부 객체가 산 넘고 물 건너서 커맨드 큐 참조까지 들고 있는 경우...
종료 처리 3/6있으면 제보 바람…우리는 무식하게 Case by Case로 처리했다.은탄환은 역시 없다.
종료 처리 4/6종료 중에 추가되는 커맨드는 바로 실행.
종료 처리 5/6종료 시 특별한(…) 조인 처리종료 시 조인을 두 번 한다!코드 보고 나도 놀람.
종료 처리 6/6종료시 명시적으로 순환 참조를 끊는다.
미들웨어 통합 사례Middleware Integration
스레드에 안전하지 않다!최신 버전에서는 아래 내용이 다 쓸데없을 지도…진짜 예전 버전을 쓰고 있다(1.7.X)!!!!스트링 캐시 테이블 등이 그냥 생 전역변수.그래서 싱글 스레드로 처리중요한 캐릭터만 표정을 사용하므로 ...
인스턴스 단위로 스레드에 안전하다.병렬 처리에 문제 없다버전 4.X 사용 중.
버전 7.X 사용 중.(구) KynapseWorld 단위로 스레드 안전성을 보장한다.역시 별 문제 없다.
사용하는 스레드가 제한적이라서 별 문제가 없다.
문서화도 잘 되어있고 아무튼 좋다.일반적으로 스레드에 안전하지 않지만,레이 캐스팅 같은 일부 함수들은 안전하다.
버전 4.X를 사용 중.3.X는 스레드에 안전하지 않다.sdk 렌더러 소스를 싹 고쳐서 모든 디바이스 접근을 커맨드 버퍼링 했었다.(경험있고 유능한) 프로그래머라면 2~3일 걸린다.웬만하면 최신 버전을 사는 것이 정신 ...
버전 6.X 사용 중버전 5.X디바이스 스레드에서 실행했기 때문에,모든 sdk 접근을 콜백으로 해야 해서 코드가 매우 복잡했다.버전 6.Xsdk의 렌더러 API를 재구현해서 렌더 스레드에 붙였다.스레드에 안전한 것 같다...
정리Summary
M2 클라이언트 스레딩 아키텍쳐이 상황인 분들에게 조금이라도 도움이 되길 바랍니다.오래된 프로젝트 + 자체 엔진 + 멀티 스레딩 구현이 발표 내용 정도만 구현해도 쿼드코어까지 잘 지원한다.게임 로직 병렬화를 잘 연구하면...
참고자료References
“Magic and Technology: Migrating from One to Many Cores in Shadowrun”. Gamefest2007“Multicore Programming Two Years Later”...
QnA발표자료는devcat.nexon.com/publication.html
전형규, M2 클라이언트 스레딩 아키텍쳐, NDC2013
Upcoming SlideShare
Loading in...5
×

전형규, M2 클라이언트 스레딩 아키텍쳐, NDC2013

2,967

Published on

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

No Downloads
Views
Total Views
2,967
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
0
Comments
0
Likes
17
Embeds 0
No embeds

No notes for slide

Transcript of "전형규, M2 클라이언트 스레딩 아키텍쳐, NDC2013"

  1. 1. M2 클라이언트 스레딩 아키텍쳐넥슨 코리아 N스퀘어개발본부 1실 GTR팀전형규henjeon@nexon.co.kr2013.04.24
  2. 2. 발표자 소개넥슨 10년차 프로그래머현재 GTR팀 팀장참여 프로젝트마비노기XBOX360 마비노기마비노기 2주요 관심사Computer GraphicsReal-Time Rendering
  3. 3. 발표 내용M2 클라이언트의 멀티 스레딩 구조를 설명스레딩 사례 소개미들웨어 통합 가이드다루지 않는 것서버 스레딩멀티 스레드 API 사용법각종 잠금 장치 사용법락프리(Lock-Free) 알고리즘
  4. 4. M2 소개M2 엔진 이름사용 중인 미들웨어들
  5. 5. 자주 등장하는 용어M2 : 마비노기2 클라이언트 프로그램스레드Thread , 싱글 스레드Single Thread, 멀티 스레드Multithread조인Join : 동기화리졸브Resolve : 커맨드 큐Command Queue 실행병렬화Parallelization작업Task로직Logic : 게임 로직 코드스레드 안전성Thread Safety코어Core디바이스Device : Direct3D 디바이스
  6. 6. 개요Overview
  7. 7. 개발 방향매우 보수적이고 안전한 스레딩 모델을 택함싱글 스레드 구조로 시작한 오래된 프로젝트.프로젝트 시작 시 상용엔진을 사용하다가 제거하고 직접 제작했다.
  8. 8. 구조 개요기능 수준 병렬화Functional Parallelization로직 -> 렌더 -> D3D의 파이프라인일부 작업들은 데이터 병렬화Data Parallelization애니메이션, 파티클, …두 개의 백그라운드 작업 스레드작업 우선 순위 차이평균 3개의 코어를 사용, 일부 구간에서 모든 코어 사용
  9. 9. 압니다, Task Parallelism하면 좋지요… 그러나…경험 있는 프로그래머가 많이 필요.작업 분할Task Partitioning 은 어렵다.디버깅이 어렵다.물론 익숙해지면 쉽겠지만…미들웨어 통합은 어떻게?업계 표준 작업 관리 라이브러리가 필요.
  10. 10. 두 가지가 없다메인 스레드가 없다.프로세스가 생성될 때 만들어진 기본 스레드라는 의미만 존재..전담Dedicated 스레드가 없다.대신, 스레드 타입이 있다: 로직, 애니메이션, 렌더, 백그라운드예외: 디바이스 스레드(윈도 및 D3D 디바이스가 생성된 스레드)
  11. 11. 스레드 관리Intel TBB(Threading Building Blocks) 사용.TBB가 아닌 OS 스레드를 사용하는 영역• 백그라운드 작업디스크 IO비동기 처리• 전담 스레드미들웨어 내부에서 생성하는 작업 스레드네트워크 IO 작업 스레드시스템 로더(부팅)
  12. 12. 스레드 통신메시지(커맨드) 큐를 사용한 단방향 통신로직 -> 렌더 -> 디바이스Single Reader, Multiple Writer역방향 통신은 조인 구간에서 처리.로직과 디바이스 스레드는 서로 통신하지 않는다.디바이스 스레드는 메시지를 받기만 한다.
  13. 13. 렌더링은 로직보다 1프레임 늦게 간다로직은 렌더링 객체에 직접 접근할 수 없다.프록시Proxy를 통해서 간접적으로 접근한다.조인 구간에서 직접 접근할 수 있다.로직에서 처리한 렌더링 작업은 그 다음 프레임에서 실행된다.프록시 객체마다 더블 버퍼링되는 커맨드 큐를 갖고 있다.최소 1프레임, 최대 2프레임의 지연이 발생.1(로직->렌더) + 1(입력 지연. 있거나 없거나 함)60fps 기준으로 18~36ms사람이 인지할 수 있는 지연 시간은 평균적으로 100ms이라고 함• 프로게이머, 굇수 제외
  14. 14. 렌더 – 디바이스 스레드 통신두 가지 모드가 있다.싱글 버퍼링(기본값)더블 버퍼링싱글 버퍼링(푸시 버퍼Push Buffer) 모드잠시 버퍼링한 후에 디바이스 커맨드 버퍼로 전송.더블 버퍼링 모드커맨드를 쌓아둔 후에 조인할 때 디바이스 커맨드 버퍼와 교체.즉, 모든 명령어는 다음 프레임에 실행된다.
  15. 15. 두 가지 방식의 특징싱글 버퍼링은 약간 느리다.렌더 스레드에서 최초의 명령을 보내기까지 수 ms 걸린다.이 시간 동안 디바이스 스레드가 공회전한다.더블 버퍼링은 결과가 1프레임 지연된다.60fps 이상에서는 거의 느껴지지 않는다.
  16. 16. 런타임에 모드를 선택일명, 스마트 버퍼링60fps 이상에서는 더블 버퍼링한다.그 미만에서는 싱글 버퍼링.
  17. 17. 자원의 스레드 안전성대부분의 자원은 특정 스레드에서만 사용되므로 안전하다.생성 후에 그 내용을 변경할 수 없다.내용을 변경하고 싶다면 사본을 생성한다.동시에 사용하는 자원은 불변Immutable 타입으로 제한.1/3
  18. 18. 자원의 스레드 안전성 2/3생성된 스레드와 파괴된 스레드가 다를 수 있다.참조 카운터로 자원의 수명을 관리한다.D3D 자원이 좋은 예.특정 스레드에서 파괴되어야 하는 자원에 주의하자.
  19. 19. 실행 중인 스레드가디바이스 스레드인지 조사자원의 스레드 안전성 3/3
  20. 20. 세부 사항Implementation Details
  21. 21. Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveCullParticle ParticleFrame N+1RenderBackground Worker #2Background Worker #1D3DDeviceValidationM2 클라이언트 태스크 다이어그램(2013/04/24)
  22. 22. 작업의 스레드 타입ThreadType::SimulationThreadType::AnimationThreadType::RenderThreadType::Background모든 태스크는 다음 타입 중 하나 ‘이상’을 가진다.로직 = Simulation애니메이션 Simulation | Animation렌더 = Render백그라운드 = Background조인 = 전부 다(ThreadType::DontCare)타입 예:
  23. 23. 스레드 어피니티AffinityWindows API를 호출하는 작업D3D를 사용하는 작업어떤 작업은 반드시 정해진 스레드에서 실행되어야 한다.다음 작업들은 디바이스 스레드에서 실행된다.Debug ConsoleProcessingInput DevicesProcessingD3DQueueResolveD3DDeviceValidation나머지 작업들은 어피니티가 없다.
  24. 24. TBB와 스레드 타입디바이스, 백그라운드 작업은 직접 생성한 스레드에서 실행.좀 더 세밀한 제어가 필요하기 때문.나머지 작업들은 모두 TBB 스케줄러로 실행한다.
  25. 25. D3DDeviceValidation 디바이스 검사D3D 디바이스 로스트, 창 크기 변경 등의 이벤트를 처리한다.자주 발생하는 이벤트가 아니므로 싱글 스레드로 처리한다.
  26. 26. Debug ConsoleProcessing 디버그 콘솔 처리개발 전용 기능들을 처리한다.싱글 스레드로 실행한다.빠르고 쉽게 만들 수 있다.디버깅하기 쉽다.배포 버전에서 제거되므로 유저 경험에 영향을 주지 않는다.
  27. 27. 입력장치 처리Input DevicesProcessingWindows API의 스레딩 문제를 피하기 위해서,싱글 스레드로 실행.수행 시간이 1ms 안쪽이므로 성능에 영향이 없다.
  28. 28. Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveCullParticle ParticleFrame N+1RenderBackground Worker #2Background Worker #1D3DDeviceValidationM2 클라이언트 태스크 다이어그램
  29. 29. 1/4로직Logic모든 게임 로직은 이 태스크에서 실행된다.게임 상태 갱신네트워킹AI사운드UI
  30. 30. 2/4로직Logic렌더링 명령은 프록시 객체를 통해서 처리실제 객체는 렌더 스레드 안에 있다.프록시 객체는 큐에 명령을 쌓아두기만 한다.명령 실행은 에서.더블버퍼링된다: 조인 구간에서 버퍼 교체.RenderQueueResolve프록시 커맨드 큐는 객체마다 존재최대 병렬화를 위해서.큐에 들어온 순서대로 실행되지 않기 때문에 비결정론적nondeterministic이다.
  31. 31. 3/4로직Logic프록시 객체는 단순히 메시지만 전달Delegation실제 수행전달
  32. 32. 4/4로직Logic상태 질의state query는 금지.어쩔 수 없이 필요한 경우에는…조인 구간에서 동기화 하거나 프록시에 동일하게 구현
  33. 33. 물리Physics로직 스레드 타입이다.ThreadType::Physics 이런 거 없다.오직 로직 스레드에서만 물리를 처리한다.렌더 스레드는 물리를 모른다.레이 캐스팅은 예외• raycastClosestShape() 등의 몇 가지 PhysX 함수들은 스레드에 안전.• 길찾기, 캐릭터 IK(Inverse Kinematics), 파티클 등에서 사용.
  34. 34. 1/3애니메이션Animation캐릭터 단위로 병렬 처리한다.tbb::parallel_for()를 쓰면 간단하지만…작업 스레드 수를 제어하고 싶어서 간단하게 직접 구현.캐릭터 목록이 담긴 배열의 주소를각 태스크가 경쟁적으로 증가시킨다.
  35. 35. 2/3애니메이션Animation캐릭터 사이의 의존성 분석이 필요서로 참조하고 있는 캐릭터들을 동시에 처리하면,race가 발생하거나 크래시될 수 있다.의존성 있는 캐릭터들은 하나의 태스크에서 순서대로 처리한다.
  36. 36. 3/3Animation코드 예:애니메이션
  37. 37. Frame NDebug ConsoleProcessingInput DevicesProcessingD3DQueueResolveAnimation AnimationJoinLogicPhysicsRenderQueueResolveCullParticle ParticleFrame N+1RenderBackground Worker #2Background Worker #1D3DDeviceValidationM2 클라이언트 태스크 다이어그램
  38. 38. 로직 스레드에서 전송한 커맨드를 실행한다.렌더링 큐 리졸브RenderQueueResolve
  39. 39. 코드 예:
  40. 40. 컬링Cull일반적인 CPU 시야 컬링 View Frustum Culling 수행.GPU로 가려진 물체 컬링Occlusion Culling 수행Hierarchical Occlusion Map 기반.자세한 설명은 생략.커서 선택Picking 처리도 이 구간에서 수행된다.
  41. 41. 파티클Particle애니메이션과 동일하게 병렬화했다.로직과 밀접한 관계가 있는 파티클 타입은 에서 처리한다.Logic예: 동전 효과
  42. 42. 1/3렌더링Render장면 업데이트, 렌더링 파이프라인 실행.디바이스 커맨드 큐로 명령어들을 전송한다.코드 예:
  43. 43. 2/3렌더링Render미룰 수 있는 작업은 최대한 디바이스 스레드로 미룬다.게을러서 병목이 아니라서 지켜보고 있는 중.병렬화할 여지가 많다
  44. 44. 3/3렌더링RenderD3D 디바이스 자원에 직접 접근하고 싶을 때 콜백Callback을 사용.코드 예:
  45. 45. 디바이스 큐 리졸브D3DQueueResolve렌더 스레드가 전송한 명령어들을 실행한다.실제로 D3D를 사용하는 구간이다.프레임 버퍼 크기 변경 처리윈도 크기 변경 등으로 프레임 버퍼 크기가 변경되면,명령어가 생성된 시점의 버퍼 크기와 실제 크기가 다르다.그냥 억지로 렌더링한 다음에 프레임 버퍼를 검게 칠한다.
  46. 46. 1/2Join 조인각 작업의 상태를 동기화하는 단계로,이 구간을 가급적 빨리 실행해야 성능에 유리하다.스레드에 안전한 구간각종 코드들의 축제가 벌어진다.현재 이 코드는 남아 있지 않다…
  47. 47. 2/2Join 조인커맨드의 실행 순서에 주의!!스레드에 안전하다고 커맨드 큐 없이 바로 상태를 조작하면,실행 순서가 역전된다.이를 방지하기 위해서 조인 구간에서 프록시의 본체에 접근할 경우,커맨드 큐를 먼저 실행한다.• 의도하지 않은 커맨드 큐 실행이 발생할 수 있다.
  48. 48. BackgroundWorker 백그라운드 작업비동기Asynchronous 처리, 파일 IO 작업.두 작업 스레드가 있다낮은 우선순위 작업• 리소스 로딩, 파일 IO• 이미지 업로드높은 우선순위 작업• 캐릭터 커스터마이징
  49. 49. 정리DebugConsoleProcessingInputDevicesProcessingD3DQueue ResolveAnimationJoinLogic PhysicsRenderQueueResolve CullParticleParticleRenderBackground Worker #2Background Worker #1D3DDeviceValidationAnimation가로축이 시간, 세로 축이 스레드이게 더 보기 쉽잖아…
  50. 50. 디버깅Debugging Tips
  51. 51. 스레드 타입 검사다른 스레드에서 실행되는 코드에 접근할 수 없도록 설계를 잘 해야 한다.그래도 어떻게든 용케 방법을 찾아서 호출하더라…작업 시작 시 자기 타입을 TLSThread Local Storage에 기록하고,주요 함수마다(…) 매크로 함수로 스레드 타입을 검사.매우 위험하고 실수의 여지가 많다.
  52. 52. 스레드 프로파일링스레드 작업 막대 그래프1/2
  53. 53. 스레드 프로파일링타이밍 그래프2/2
  54. 54. 스레딩 옵션 변경최소한 시작 옵션으로 지정할 수 있어야 한다.런타임에 변경할 수 있으면 스레딩 성능 비교 및 디버깅에 유용.여러 개의 클라이언트를 실행할 경우, 싱글 스레드 모드가 편하다.
  55. 55. 커맨드 생성 위치 추적커맨드가 생성되는 시점과 실행 시점이 다르다.커맨드 실행 코드에 브레이크 포인트를 걸어봤자 얻는 것이 별로 없다.커맨드를 추가하는 위치가 더 중요하다.커맨드에 ID를 붙이고, 특정 ID를 생성하는 위치를 찾는다.자원 생성 커맨드는 유니크 id를 부여하기 쉽다.일반 커맨드는 시퀀스 넘버를 붙이기 때문에 버그의 재현 법이 중요.
  56. 56. 코드 예:
  57. 57. 스레딩 사례 소개Case Study
  58. 58. 렌더링 리소스 로딩필연적으로 비동기 작업이다.보통, 렌더 스레드가 리소스를 요청하지만,렌더 스레드는 디바이스 자원을 생성할 수 없다.여러 스레드가 참여하는 파이프라인 구조가 필요하다.텍스쳐 로딩 파이프라인 구현 사례 소개.
  59. 59. 텍스쳐 로딩 과정 개요Texture DescriptorLoadingTextureRequested Cache Generation File LoadingDevice TextureCreationTextureLockingmemcpy()TextureUnlockingFinalization Rendering!
  60. 60. 디스크립터 로딩Texture DescriptorLoading텍스쳐에 대한 정보가 담긴 작은 파일을 바로 로딩한다.텍스쳐 크기, 형식 등은 요청 즉시 반환한다.32*32 썸네일Thumbnail도 같이 로딩해서 로딩 완료 전까지 사용한다.렌더 스레드에서 실행.
  61. 61. 캐시 파일 생성개발 클라이언트 전용 기능.백그라운드 스레드에서 실행한다.어셋 저장소Repository에는 무압축 원본 텍스쳐만 올리고,런타임에 텍스쳐를 가공한다.매번 변환하면 너무 느리므로 캐시를 만들어둔다.CacheGeneration디스크립터, 캐시가 없다면 이 단계에서 바로 생성한다.
  62. 62. 파일 로딩백그라운드 스레드에서 실행.File Loading텍스쳐 파일을 열고 원하는 밉맵을 메모리로 복사한다.
  63. 63. 디바이스 텍스쳐 생성디바이스 스레드에서 실행.D3D 텍스쳐 객체를 생성한다.Device TextureCreation
  64. 64. 텍스쳐 락 걸기디바이스 스레드에서 실행.텍스쳐에 락을 걸고 밉맵 복사를 준비한다.TextureLocking
  65. 65. 이미지 복사백그라운드 스레드에서 실행.락을 걸고 받아 둔 주소에 파일 내용을 복사한다.memcpy()
  66. 66. 텍스쳐 락 풀기디바이스 스레드에서 실행.언락한다. 텍스쳐를 사용할 준비를 거의 다 했다.TextureUnlocking
  67. 67. 마무리조인 구간에서 실행.Finalization각종 자원 해제 및 뒷정리 단계.파이프라인이 중간에 취소되어도 이 단계는 무조건 실행된다.
  68. 68. 렌더링 리소스 로딩 - 정리렌더링 리소스 로딩은 여러 스레드를 통과하는 복잡한 작업이다.스레드 어피니티를 고려하는 파이프라인 구현이 필요.찾아봐도 용도에 딱 맞는 구현이 없다.만들기 어렵지 않아서 다행이다.
  69. 69. 종료 처리 1/6게임 루프 전환, 종료 시 커맨드 큐의 정리는 언제나 골치 아프다.히스토리 채널. Vikings. 2013대충 종료하면 발할라에 갈 수 없다.
  70. 70. 종료 처리 2/6실행할 것이냐 그냥 버릴 것이냐쌓인 커맨드의 처리 문제그냥 버린다 / 바로 실행한다 / 큐에 넣는다종료 처리 중에 발생하는 커맨드커맨드 내부 객체가 산 넘고 물 건너서 커맨드 큐 참조까지 들고 있는 경우.순환 참조
  71. 71. 종료 처리 3/6있으면 제보 바람…우리는 무식하게 Case by Case로 처리했다.은탄환은 역시 없다.
  72. 72. 종료 처리 4/6종료 중에 추가되는 커맨드는 바로 실행.
  73. 73. 종료 처리 5/6종료 시 특별한(…) 조인 처리종료 시 조인을 두 번 한다!코드 보고 나도 놀람.
  74. 74. 종료 처리 6/6종료시 명시적으로 순환 참조를 끊는다.
  75. 75. 미들웨어 통합 사례Middleware Integration
  76. 76. 스레드에 안전하지 않다!최신 버전에서는 아래 내용이 다 쓸데없을 지도…진짜 예전 버전을 쓰고 있다(1.7.X)!!!!스트링 캐시 테이블 등이 그냥 생 전역변수.그래서 싱글 스레드로 처리중요한 캐릭터만 표정을 사용하므로 아직까지는 별 문제 없었다.런칭 후에 왠지 폭탄이 터질 것 같다…
  77. 77. 인스턴스 단위로 스레드에 안전하다.병렬 처리에 문제 없다버전 4.X 사용 중.
  78. 78. 버전 7.X 사용 중.(구) KynapseWorld 단위로 스레드 안전성을 보장한다.역시 별 문제 없다.
  79. 79. 사용하는 스레드가 제한적이라서 별 문제가 없다.
  80. 80. 문서화도 잘 되어있고 아무튼 좋다.일반적으로 스레드에 안전하지 않지만,레이 캐스팅 같은 일부 함수들은 안전하다.
  81. 81. 버전 4.X를 사용 중.3.X는 스레드에 안전하지 않다.sdk 렌더러 소스를 싹 고쳐서 모든 디바이스 접근을 커맨드 버퍼링 했었다.(경험있고 유능한) 프로그래머라면 2~3일 걸린다.웬만하면 최신 버전을 사는 것이 정신 건강에 좋다.4.X는 스레드에 안전하다.모든 렌더링 명령이 내부 커맨드 큐에 쌓인다 업데이트와 렌더링을 다른 스레드에서 실행할 수 있다.따로 해줄 것이 거의 없다.
  82. 82. 버전 6.X 사용 중버전 5.X디바이스 스레드에서 실행했기 때문에,모든 sdk 접근을 콜백으로 해야 해서 코드가 매우 복잡했다.버전 6.Xsdk의 렌더러 API를 재구현해서 렌더 스레드에 붙였다.스레드에 안전한 것 같다.• 통합한지 얼마 되지 않아서 확신이 없다.
  83. 83. 정리Summary
  84. 84. M2 클라이언트 스레딩 아키텍쳐이 상황인 분들에게 조금이라도 도움이 되길 바랍니다.오래된 프로젝트 + 자체 엔진 + 멀티 스레딩 구현이 발표 내용 정도만 구현해도 쿼드코어까지 잘 지원한다.게임 로직 병렬화를 잘 연구하면 진정한 태스크 병렬화도 가능할 것이다.멀티 스레딩, 어렵지 않다.
  85. 85. 참고자료References
  86. 86. “Magic and Technology: Migrating from One to Many Cores in Shadowrun”. Gamefest2007“Multicore Programming Two Years Later”. Gamefest2007“Memory Models: Foundational Knowledge for Concurrent Code”. Gamefest2008“What’s in a Frame: Latency, Tearing, Jitter, and Frame Rate on Xbox 360”. Gamefest2011“Scaling Your Game to n Cores: A Deep Dive on Tasking”. Gamefest2011“Getting More From Multicore”. GDC2008“Optimizing DirectX on Multi-core architectures”. GDC2008“Optimizing Game Architectures with Intel® Threading Builing Blocks”. GDC2008“The Future of Programming for Multi-core with the Intel C++ Compilers”. GDC2008“Comparative Analysis of Game Parallelization”. GDC2008“Threading QUAKE 4 & Enemy Territory QUAKE Wars”. GDC2008“Optimizing Game Architectures with Intel Threading Building Blocks”. GDC2009“Task-based-Multithreading – How to Program for 100 cores”. GDC2010“Firaxis Civilization V: A Case Study in Scalable Game Performance”. GDC2010“Don’t Dread Threads”. GDC2010“Streaming Massive Environments. From Zero to 200MPH”. GDC2010“DirectX11 Rendering In Battlefield3”. GDC2011“Hotspots, FLOPS, and uOps:To-The-Metal CPU Optimization”. GDC2011“Multi-Core Memory Management Technology in Mortal Kombat”. GDC2011“Terrain In Battlefield 3: A Modern, complete and scalable system”. GDC2012Joe Waters.Ian Lewis.Herb Sutter.David Cook.Steve Smith, Leigh Davies.Ian Lewis.Leigh Davies.Brad Werth.Ganesh Rao.Dmitry Eremin.Anu Kalra, Jan Paul van Waveren.Brad Werth.Ron Fosner.Dan Baker, Yannis Minadakis.Orion Granatir, Omar Rodriguez.Chris Tector.Johan Andersson.Stan Melax, Deppak Vembar.Adisak Pochanayon.Mattias Widmark.
  87. 87. QnA발표자료는devcat.nexon.com/publication.html

×