[퍼옴] Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)

웬만해서는 블로그에 퍼온 내용은 올리지 않을려고 했는데…

이건 올려야 해 ( __ __)\

NDC 2014 세션 키노트:

SlideShare 사이트 링크:

 

1. 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지) 정내훈 한국산업기술대학교 게임공학과
2. 발표자 소개 : KAIST 전산과 박사 − 전공 : 멀티프로세서 CPU용 일관성 유지 HW : NCSoft 근무 − Alterlife 프로그램 팀장 − Project M(현 Blade & Soul) 프로그램 팀장 − CTO 직속 게임기술연구팀 : 현 : 한국산업기술대학교 게임공학과 부교수 − 학부 강의 : 게임서버프로그래밍, 멀티코어프로그래밍 − 대학원 강의 : 멀티코어프로그래밍, 심화 게임서버 프로그래밍 2-2
3. 참고 : 삼성 첨단기술연수소와 CJ E&M에서 강의중인 내용 반영 − 40시간 강의 (실습 포함) => 뒷부분 만 발췌 : 대학원 4주 강의 분량의 압축 2-3
4. 2-4 목차 : 도입 : 그래도 멀티쓰레드 프로그램을 하시려구요? : 대책 : 성능 : 미래 또는 현실
5. 도입 : 제가 하는 발표 들어본 적이 있으신 분? : 멀티쓰레드 프로그래밍 경험 있으신 분? : Lock-free 자료 구조가 무엇인지 아시는 분? 2-5 CJ E&M, NDC, KGC, 삼성, JCE
6. 도입 : 멀티쓰레드 프로그래밍의 위험성 − “자꾸 죽는데 이유를 모르겠어요” | 자매품 : “이상한 값이 나오는데 이유를 모르겠어요” − “더 느려져요” 2-6 [미] MuliThreadProgramming [mʌ́ltiθred-|proʊgrӕmɪŋ] : 1. 흑마술, 마공 2. 위력이 강대하나 다루기 어려워 잘 쓰이지 않는 기술
7. 도입 : 멀티쓰레드 프로그래밍의 어려움 (한페이지 요약) − Data Race : 2를 5천만번 더했는데 1억이 안 나오는 경우. | Lock을 사용해 해결 − 성능 : 싱글 쓰레드 버전보다 더 느림 | Lock 쓰지 말자 − 컴파일러 : 변수를 참조했는데, 컴파일러가 무시 | volatile 키워드로 해결, atomic으로 해결 − CPU : 프로그램 실행순서를 자기 마음대로 변조 | asm mfence; 명령으로 해결, atomic으로 해결 − Cache : -1을 썼는데 65535가 써짐 | 포인터 주소 확인하기. 2-7 ABA 문제는?
8. 목차 : 도입 : 대책 : Lock-free 프로그래밍이 도대체 뭐야? : 성능 : 미래 또는 현실 2-8
9. 대책 : 현실의 멀티쓰레드 프로그램은? − 멀티 코어에서 여러 쓰레드가 동시에 실행된다. − 쓰레드간의 데이터 공유 및 동기화는 안전한 Lock-free 자료구조를 통해서 이루어진다. − 언리얼 3 : 디스플레이 리스트 Queue − 각종 게임 서버 : Job Queue외 다수 2-9
10. 대책 : Lock-free 알고리즘을 사용하여야 한다. : 사용하지 않으면 − 병렬성 감소 − Priority Inversion − Convoying − /* 성능이 떨어지고 랙이 발생한다 */ − /* 작년에 보여드렸어요~~ */ 10
11. 대책 : Lock-free 알고리즘이란? − 여러 개의 쓰레드에서 동시에 호출했을 때에도 정해진 단위 시간마다 적어도 한 개의 호출이 완료되는 알고리즘. 11 ??????
12. 대책 : Lock-free 알고리즘이란? − 자료구조 및 그것에 대한 접근 방법 | 예) QUEUE : enqueue, dequeue | 예) STACK : push, pop | 예) 이진 트리 : insert, delete, search 12
13. 대책 : Lock-free 알고리즘이란? − 멀티쓰레드에서 동시에 호출해도 정확한 결과를 만들어 주는 알고리즘 | STL 탈락. − Non-Blocking 알고리즘 | 다른 쓰레드가 어떤 상태에 있건 상관없이 호출이 완료된다. − 호출이 다른 쓰레드와 충돌하였을 경우 적어도 하나의 승자가 있어서, 승자는 delay없이 완료 된다.
14. 대책 : (보너스) − Wait-free 알고리즘은? | 호출이 다른 쓰레드와 충돌해도 모두 delay없이 완료 된다. : 추가 상식 − LOCK을 사용하지 않는다고 lock-free 알고리즘이 아니다!!! − LOCK을 사용하면 무조건 lock-free알고리즘이 아니다.
15. 대책 : 알고리즘의 분류 2-15 알고리즘 싱글쓰레드 멀티쓰레드 Blocking Non-blocking Lock-free Wait-free …. ….
16. 대책 : 예) Blocking 알고리즘 2-16 mylock.lock(); sum = sum + 2; mylock.unlock();mylock.lock(); q.push(35); mylock.unlock(); while (dataReady == false); _asm mfence; temp = g_data;
17. 대책 : 왜 Blocking인가? − dataReady에 true가 들어가지 않으면 이 알고리즘은 무한 대기, 즉 다른 쓰레드에서 무언가 해주기를 기다린다. − 여러 가지 이유로 dataReady에 true가 들어오는 것이 지연될 수 있다. | Schedule out, 다른 쓰레드 때문에 대기 2-17 while (dataReady == false); temp = g_data;
18. 대책 : Non-blocking은? 2-18 _asm lock add sum, 2; LF_QUEUE::push(int x) { Node *e = new_Node(x); while (true) { Node *last = tail; Node *next = last->next; if (last != tail) continue; if (NULL == next) { if (CAS(&(last->next), NULL, e)) { CAS(&tail, last, e); return; } } else CAS(&tail, last, next); } } mylock.lock(); q.push(x); Mylock.unlock();
19. 대책 : Non-blocking은? 2-19 if (dataReady == false) return false; _asm mfence; temp = g_data;
20. 대책 : CAS? − Lock-free 알고리즘의 핵심 − CAS가 없이는 대부분의 non-blocking 알고리즘들을 구현할 수 없다. | Queue, Stack, List… − CAS를 사용하면 모든 싱글쓰레드 알고리즘 들을 Lock-free 알고리즘으로 변환할 수 있다!!! 2-20
21. 대책 : 정리 − Lock-free 알고리즘을 써야 한다. | 성능 때문이다. | CAS가 꼭 필요하다. : CAS − CAS(&A, old, new); − 의미 : A의 값이 old면 new로 바꾸고 true를 리턴 − 다른 버전의 의미 : A메모리를 다른 쓰레드가 먼저 업데이트 해서 false가 나왔다. 모든 것을 포기하라. 2-21
22. 대책 : Lock-free 알고리즘은 어떻게 구현되는가? : 알고리즘의 동작이란? − 기존의 자료구조의 구성을 다른 구성으로 변경하거나 자료구조에서 정보를 얻어내는 행위 2-22 3Head Tail 1 9X 3Head Tail 1 9X push(35); 35
23. 대책 : Lock-free 알고리즘은 어떻게 구현되는가? 23 자료구조를 변경한다. 다른 쓰레드와 충돌했는가? 완료 no yes (time machine) 시도 전으로 되돌아간다.. ???
24. 대책 : Lock-free 알고리즘은 어떻게 구현되는가? : 앞의 알고리즘이 불가능 하므로 2-24 자료구조의 변경을 시도한다. but, 다른 쓰레드가 먼저 변경했으면 시도 취소. 성공했는가? 완료 yes no 현재의 자료구조를 파악한다.
25. 대책 2-25 자료구조의 변경을 시도한다. but, 다른 쓰레드가 먼저 변경했으면 시도 취소. CAS while (true) { int old_sum = sum; if (true == CAS(&sum, old_sum, old_sum+2)) break; } mylock.lock(); sum = sum + 2; mylock.unlock();
26. 대책 2-26 자료구조의 변경을 시도한다. but, 다른 쓰레드가 먼저 변경했으면 시도 취소. CAS LF_QUEUE::push(int x) { Node *e = New_Node(x); while (true) { Node *last = tail; Node *next = last->next; if (last != tail) continue; if (NULL != next) continue; if (CAS(&(last->next), NULL, e, &tail, last, e)) return; } } QUEUE::push(int x) { Node *e = new Node(x); tail->next = e; tail = e; }
27. 대책 2-27 현실 LF_QUEUE::push(int x) { Node *e = New_Node(x); while (true) { Node *last = tail; Node *next = last->next; if (last != tail) continue; if (NULL != next) continue; if (CAS(&(last->next), NULL, e, &tail, last, e)) return; } } LF_QUEUE::push(int x) { Node *e = New_Node(x); while (true) { Node *last = tail; Node *next = last->next; if (last != tail) continue; if (NULL == next) { if (CAS(&(last->next), NULL, e)) { CAS(&tail, last, e); return; } } else CAS(&tail, last, next); } } 하지만 2개의 변수에 동시에 CAS를 적용할 수 는 없다!
28. 대책 : … − 알고리즘이 많이 복잡하다. − 그래서 작성시 실수하기가 쉽다. − 실수를 적발하기가 어렵다. | 하루에 한두 번 서버 크래시 | 가끔 가다가 아이템 증발 − 제대로 동작하는 것이 증명된 알고리즘을 사용해야 한다. 2-28
29. 대책 : Lock-Free 알고리즘의 장단점 − 장점 | 성능!! | 높은 부하에도 안정적!! − 단점 | 생산성 : 복잡한 알고리즘 | 신뢰성 : 정확성을 증명하는 것은 어렵다. | 확장성 : 새로운 메소드를 추가하는 것이 매우 어렵다. | 메모리 : 메모리 재사용이 어렵다. ABA문제 2-29
30. 대책 : 결론 − 믿을 수 있는 non-blocking container들을 사용하라. | Intel TBB, Visual Studio PPL − 자신을 포함한 출처가 의심스러운 알고리즘은 정확성을 증명하고 사용하라. | 정확성이 증명된 논문에 있는 알고리즘은 OK. 2-30
31. 목차 : 도입 : 대책 : 성능 : 데스크탑에서 동접 1만 서버를 만들어 보자. : 미래 또는 현실 2-31
32. 성능 : 간단한 MMORPG 서버를 만들어 보자 − 1000×1000 world − 60×60 sector − 시야 30 − 1000마리의 몬스터 | 플레이어 접근 시 자동 이동 및 공격 − 이동/공격/채팅 가능 − Windows에서 IOCP로 구현 − <2013년 게임서버프로그래밍 텀프로젝트 by 임상수>
33. 성능 : 성능 향상을 위해 − 시야 처리시 검색 성능을 위해 월드를 sector로 분할하여 주위 sector만 검색 | 병렬 검색을 위해 tbb::concurrent_hash_map 사용 − 몬스터 AI 처리를 모든 쓰레드에서 균등하게 나누어 처리하기 위해 timer와 event시스템 사용 | timer queue 병렬 등록을 위해 tbb::concurrent_priority_queue를 사용 − 객체 id의 재사용을 막고 메모리 재사용을 위해 객체 id와 객체 배열의 인덱스를 쌍으로 관리 | <id, index>의 병렬 검색을 위해 tbb::concurrent_hash_map 사용
34. 성능 : 성능 측정용 DummyClient − 서버에게 부하를 걸 수 있도록 여러 명의 Player를 에뮬레이션 해주는 프로그램 − 사람이 플레이 하는 것과 비슷하게 Player를 주기적으로 랜덤한 방향으로 이동 시킴 − 한명의 유저당 하나의 소켓 연결을 하므로 서버에서는 일반 클라이언트 접속과 DummyClient접속이 서로 차이가 없음 − 훌륭한 UNIT TESTER − /* 이것도 IOCP로 구현, Direct3D로 유저 분포 실시간 디스플레이 */
35. 성능 2-35
36. 성능 : 실행 결과 − Intel i7 920 2.67GHz 머신에서 실행 | 동접 8000 정도까지 − Lock-free 자료구조로 최적화 하기 전에는 동접 3000 정도가 한계.
37. 목차 : 도입 : 대책 : 성능 : 미래 또는 현실 : Transactionl 메모리, 그리고… 2-37
38. 현실 : C++11 − 멀티쓰레드 프로그래밍 API의 표준화 − 멀티 쓰레드 표준 라이브러리 | <thread>, <mutex>, <atomic>, <memory> − Boost ASIO − G++ 4.8, Visual Studio 2013 | 아직 최적화에 문제 : shared_ptr, mutex
39. 현실 : 멀티쓰레드 프로그래밍 도우미 (1/2) − Intel TBB | 좋은 성능, 유용한 라이브러리 | Concurrent 자료 구조 − Visual Studio PPL | Intel TBB와 유사 − OpenMP | 컴파일러 레벨에서 병렬 프로그램 API를 제공 | 성능과 골치 아픈 문제들은 그대로
40. 현실 : 멀티쓰레드 프로그래밍 도우미 (2/2) − CUDA, OpenCL, DirectCompute | 멀티코어 활용이 아니라 GPU활용 | 렌더링 하느라 바쁜 GPU를 건드리지 마세요. | I/O처리가 불가능해서 서버 Core Logic에는 적용 불가능 − 그 외, CK(Concurrency Kit), Noble Library…
41. 현실 : 암담한 현실 − Blocking Algorithm | 성능 저하, priority inversion, convoying | Deadlock! − Non-blocking Algorithm | 높은 난이도로 인한 생산성 저하 : 어쩌라고???? Transactional Memory!
42. 현실 : Transactional Memory? 2-42 Tim Sweeny at GameTech2010
43. 현실 : Transactional Memory는 − 아까 본 바로 그 그림을 그대로 구현한 것… 자료구조를 변경한다. 다른 쓰레드와 충돌했는가? 완료 no yes (time machine) 시도 전으로 되돌아간다.. ???
44. 현실 : Transactional Memory가 좋은 이유? − 생산성! − 싱글쓰레드 프로그램을 그대로 쓸 수 있음 | 하지만 멀티쓰레드에서 Lock-free하게 실행됨. LF_QUEUE::push(int x) { Node *e = new Node(x); atomic { tail->next = e; tail = e; } } LF_QUEUE::push(int x) { Node *e = New_Node(x); while (true) { Node *last = tail; Node *next = last->next; if (last != tail) continue; if (NULL == next) { if (CAS(&(last->next), NULL, e)) { CAS(&tail, last, e); return; } } else CAS(&tail, last, next); } } Transactionl Memory 구현
45. 현실 : Transactional Memory의 사용법 − 다른 쓰레드와 충돌이 일어날 수 있는 구간을 transaction으로 선언한다. 끝, 이게 전부
46. 현실 : 어떻게 가능한가??? 1. transaction 구간에서 읽고 쓴 메모리를 다른 쓰레드에서 접근했는지 검사한다. | write는 실제 메모리에 쓰지 않고 잠시 대기 2. 다른쓰레드에서의 접근이 있었으면 모든 업데이트를 무효화 한 후 다시 시도. 3. 다른쓰레드에서의 접근이 없었다면 transaction 구간에서의 update를 실제 메모리에 update
47. 현실 : 그게 구현 가능하다고?? : 어떻게 − Software적으로 모든 공유 메모리 업데이트를 관리하면 됨 (STM: Software Transactional Memory) − 또는, 하드웨어가 알아서 해줌 (HTM: Hardware Transactional Memory) 2-47
48. 현실 : Transactional Memory : 장점 − 높은 생산성 | 기존의 프로그램을 그대로 사용 가능 : 단점 − STM : 오버헤드로 인한 속도 저하 − HTM : HW 필요. : 한계점 − 쓰레드가 너무 많을 경우 잦은 충돌로 인한 성능향상의 한계 2-48
49. 현실 : Transactional Memory : 지금까지는 꿈속의 개념이고 8 Core이상에서 STM의 성능을 기대하는 정도였으나. : Intel에서 일을 저지름. 2-49 2013년 6월 HASWELL말매 Haswell은 HTM을 지원.
50. 현실 : Intel Haswell 2-50
51. 트랜잭션 메모리의 구현 : Haswell의 HTM − 복수개의 메모리에 대한 Transaction을 허용한다. | L1 Data Cache의 크기 만큼 − CPU에서 transaction 실패시의 복구를 제공한다. | 메모리와 레지스터의 변경을 모두 Roll-back한다. − Visual Studio 2012, update 2에서 지원 2-51
52. 트랜잭션 메모리의 구현 : 하드웨어 트랜잭션 메모리 예제 2-52 DWORD WINAPI ThreadFunc(LPVOID lpVoid) { for (int i=0;i<500000000 / num_thread;++i) { while (_xbegin() != _XBEGIN_STARTED) _xabort(0); sum += 2; _xend(); } return 0; }
53. 트랜잭션 메모리의 구현 : 하드웨어 트랜잭션 메모리 예제 (Set의 Add) 2-53 bool Add(int key) { NODE *pred, *curr; NODE *node = new NODE(key); while(true) { pred = &head; curr = pred->next; while (curr->key < key) { pred = curr; curr = curr->next; } if (_XBEGIN_STARTED != my_xbegin()) { _xabort(0); continue; } if (!validate(pred, curr)) { _xabort(0); continue; } if (key == curr->key) { _xend(); delete node; return false; } else { node->next = curr; pred->next = node; _xend(); return true; } } } HTM Lock-Free
54. 트랜잭션 메모리의 구현 : Haswell HTM의 한계 − 모든 알고리즘에 적용 불가능 | HW 용량 한계 => 알고리즘의 맞춤형 수정 필요. | Nested Transaction 불가능 => 가능은 한데… − 오버헤드 | 모든 레지스터 내용 저장 및 Roll-back 2-54 그리고…
55. 트랜잭션 메모리의 구현 2-55 휴… 밥줄 끊길뻔…
56. 미래 : HTM이 업그레이드 되어서 보급되면 끝인가? − 쓰레드가 많아 질 수록 충돌확률이 올라가 TM의 성능이 떨어진다. − 64Core 정도가 한계일 것이라고 예측하고 있다. (2010 GameTech, Tim Sweeny)
57. 미래 : 왜 쓰레드사이에 충돌이 생기는가? − C 스타일 언어를 사용하기 때문이다. | 공유 메모리 | side effect : 해결책은? C 비슷한 언어를 버린다. − 대신 함수형, 논리형 언어 사용 | 공유 메모리 없고 side effect없음
58. 새로운 언어 : 주목받고 있는 언어 − 하스켈 | 순수 함수형 언어로 1990년에 개발 | 개념은 뛰어나나 난이도로 인해 많이 사용되지 못하고 있음. − Earlang | 에릭슨에서 전자 계산기용으로 1982년에 개발 | Scalable한 서버 시스템에 자주 사용되고 있음 − Go, RUST 2010GDC
59. 새로운 언어 : 함수형 언어의 문제 − 익히기 어렵다.
60. 정리 : Lock-free 알고리즘이 무엇인가? − 어떤 조건을 만족해야 하는가? − 어떻게 구현되는가? − 왜 어려운가? − 하지만 왜 써야만 하는가. : 멀티쓰레드 프로그래밍의 미래. − Transactional Memory | 진격의 INTEL − 새로운 언어의 필요 2-60
61. NEXT : 다음 주제(내년???) − Lock-free search : SKIP-LIST − ABA Problem, aka 효율적인 reference counting − 고성능 MMO서버를 위한 non-blocking 자료구조의 활용 2-61
62. Q&A : 연락처 − nhjung@kpu.ac.kr − 발표자료 : ftp://210.93.61.41 id:ndc21 passwd: <바람의나라> − Slideshare에 올릴 예정. : 참고자료 − Herlihy, Shavit, “The Art of Multiprocesor Programming, Revised”, Morgan Kaufman, 2012 2-62

Advertisements

댓글 남기기

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s