• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)
 

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

on

  • 5,763 views

Easy to understand review of nature of the Lock-free algorithm.

Easy to understand review of nature of the Lock-free algorithm.
작년에 KGC2013에서 했던 내용의 업데이트 버전임. C++11에 맞추어 업데이트 되었음.

Statistics

Views

Total Views
5,763
Views on SlideShare
2,058
Embed Views
3,705

Actions

Likes
31
Downloads
47
Comments
0

6 Embeds 3,705

http://blog.naver.com 3364
https://twitter.com 255
http://cafe.naver.com 74
http://laidinous.tistory.com 7
http://www.slideee.com 4
http://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

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