SlideShare a Scribd company logo
1 of 95
Download to read offline
멀티쓰레드 프로그래밍이
  왜이리 힘드나요?
(컴파일러와 하드웨어에서 Lock-free
      알고리즘 까지)
          정내훈
    한국산업기술대학교 게임공학과
2-2


                발표자 소개
• KAIST 전산과 박사
  – 전공 : 멀티프로세서 CPU용 메모리 일관성 유지 HW
• NCSoft 근무
  – Alterlife , Project M 프로그램 팀장
• 현 : 한국산업기술대학교 게임공학과 부교수
  – 학부 강의 : 게임서버프로그래밍
  – 대학원 강의 : 멀티코어프로그래밍
2-3


                     목차
•   도입
•   현실
•   Welcome to the Hell.
•   새로운 희망
•   우리의 나아갈 길
•   실제성능
2-4


              도입
• 멀티쓰레드 프로그래밍 이란?
  – 멀티코어 혹은 멀티프로세서 컴퓨터의 성능을
    이끌어 내기 위한 프로그래밍 기법
  – 흑마술의 일종
   • 잘못 사용하면 패가 망신
2-5


              도입
• 흑마술 멀티쓰레드 프로그래밍의 위험성
  – “자꾸 죽는데 이유를 모르겠어요”
  • 자매품 : “이상한 값이 나오는데 이유를 모르겠어요”


 – “더 느려져요”
2-6


                     내용
•   도입
•   현실
•   Welcome to the Hell.
•   새로운 희망
•   우리의 나아갈 길
2-7


               현실
• “멀티쓰레드 안 해도 되지 않나요?”
   – NO!
   – “MultiThread 프로그래밍을 하지 않는 이상
     프로그램의 성능은 전혀 나아지지 않을 것임” –
     by Intel, AMD
   • “공짜 점심은 끝났어요~~”
2-8


               현실




피할 곳도 숨을 곳도 없습니다.
2-9


          현실
• 멀티쓰레드 프로그래밍을 하지 않으면?
  – (멀티코어) CPU가 놀아요.
  – 경쟁회사 제품보다 느려요.
  • FPS
  • 동접
2-10


                 현실
• 멀티 코어 CPU가 왜 나왔는가?
  – 예전에는 만들기 힘들어서? No
  – 다른 방법들의 약발이 다 떨어져서!
   • 클럭 속도, 캐시, 슈퍼스칼라, Out-of-order, 동적 분기
     예측…
 – 늦게 나온 이유
   • 프로그래머에게 욕을 먹을 것이 뻔하기 때문.
2-11


            현실
• 컴퓨터 공학을 전공했지만 학부에서
  가르치지 않았다.
• 큰맘 먹고 스터디를 시작했지만 한 달도 못
  가서 흐지부지 되었다. (원인은 다음 페이지)
• 그냥 멀티쓰레드 안 쓰기로 했다.
2-12


          현실
• 좋은 교재
2-13


              현실
• 왜 멀티쓰레드 프로그래밍이 어려운가?
  – 다른 쓰레드의 영향을 고려해서 프로그램 해야 하기
    때문에
  – 에러 재현과 디버깅이 힘들어서
  – Visual Studio가 사기를 치고 있기 때문
• 왜 멀티쓰레드 프로그래밍이 진짜로 어려운가?
  – CPU가 사기를 치고 있기 때문
2-14


                     내용
•   도입
•   현실
•   Welcome to the Hell.
•   새로운 희망
•   우리의 나아갈 길
2-15


                             고생길
• Visual Studio의 사기
 DWORD WINAPI ThreadFunc1(LPVOID lpVoid)
 {
          data = 1;
          flag = true;
 }



                            DWORD WINAPI ThreadFunc2(LPVOID lpVoid)
                            {
                                     while(!flag);
                                     my_data = data;
                            }
2-16


                             고생길
• Visual Studio의 사기                    DWORD WINAPI ThreadFunc2(LPVOID lpVOid)
                                       {
DWORD WINAPI ThreadFunc2(LPVOID   lpVoid)
                                       00951020 mov          al,byte ptr [flag (953374h)]
{
         while(!flag);                              while (!flag);
         my_data = data;                00951025   test        al,al
                                        00951027   je          ThreadFunc2+5 (951025h)
}                                                   int my_data = data;
                                                    printf("Data is %Xn", my_data);
                                        00951029   mov         eax,dword ptr [data (953370h)]
                                        0095102E   push        eax
                                        0095102F   push        offset string "Data is %Xn"
                                        (952104h)
                                        00951034 call          dword ptr [__imp__printf

싱글 쓰레드 프로그램이면?                          (9520ACh)]
                                        0095103A add           esp,8

    VS는 무죄!                                        return 0;
                                        0095103D xor           eax,eax
                                        }
                                        0095103F ret           4
2-17


                고생길
• Visual Studio의 사기를 피하는 방법
  – volatile을 사용하면 된다.
    • 반드시 메모리를 읽고 쓴다.
    • 변수를 레지스터에 할당하지 않는다.
    • 읽고 쓰는 순서를 지킨다.
  – 참 쉽죠?
  – “어셈블리를 모르면 Visual Studio의 사기를 알 수 없다”
   흠좀무…
2-18


                      고생길
• 정말 쉬운가???
 struct Qnode {
    volatile int data;
    volatile Qnode* next;
 };                                        무엇이 문제일까??
 DWORD WINAPI ThreadFunc1(LPVOID lpVoid)
 {
    while ( qnode->next == NULL ) { }
    my_data = qnode->next->data;
 }
고생길
• volatile의 사용법
   – volatile int * a;
      • *a = 1; // 순서를 지킴
      • a = b; // 순서를 지키지 않는다.
   – int * volatile a;
      • *a = 1; // 순서를 지키지 않음,
      • a = b; // 이것은 순서를 지킴
고생길
• Volatile 위치 오류의 예
     volatile Qnode*     next;               Qnode * volatile next;

   void UnLock() {
                Qnode *qnode;
                qnode = myNode;
                if (qnode->next == NULL) {
                              LONG long_qnode = reinterpret_cast<LONG>(qnode);
                              volatile LONG *long_tail = reinterpret_cast<volatile LONG*>(&tail);
                              if ( CAS(long_tail, NULL, long_qnode) ) return;
                              while ( qnode->next == NULL ) { }
                }
                qnode->next->locked = false;
                qnode->next = NULL;               011F1089 mov                      eax,dword       ptr [esi+4]
   }                                              011F108C lea                      esp,[esp]
                                                 011F1090       cmp                eax,ebx
                                                 011F1092       je                 ThreadFunc+90h (11F1090h)
01191090   mov             eax,dword ptr [esi+4]
01191093   test            eax,eax
01191095   je              ThreadFunc+90h (1191090h)
2-21


                                         고생길
• Thread 2개로 합계 1억을 만드는 프로그램
#include <windows.h>
#include <stdio.h>

volatile int sum = 0;

DWORD WINAPI ThreadFunc(LPVOID lpVoid)
{
           for (int i=1;i<=25000000;i++) sum += 2;
           return 0;
}                                    int main()
                                     {
                                                DWORD addr;
                                                HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &addr);
                                                HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &addr);
                                                WaitForSingleObject(hThread2, INFINITE);
                                                WaitForSingleObject(hThread3, INFINITE);
                                                CloseHandle(hThread2);
                                                CloseHandle(hThread3);
                                                printf(“Result is %dn", sum);
                                                getchar();
                                                return 0;
                                     }
2-22


           고생길
• 다중 쓰레드 - 결과
2-23


          고생길
• 결과는?
• 엉뚱한 답
2-24


            고생길
• 왜 틀린 결과가 나왔을까?
  ─ “sum+=2”가 문제이다.
2-25


                    고생길
• 왜 틀린 결과가 나왔을까?
  – DATA RACE (복수의 쓰레드에서 같은 공유 메모리에
    동시에 WRITE하려는 행위.) 때문이다.
       쓰레드 1          쓰레드 2
     MOV EAX, SUM
                                   sum = 200
     ADD EAX, 2     MOV EAX, SUM
                                   sum = 200
     MOV SUM, EAX   ADD EAX, 2
                                   sum = 202
                    MOV SUM, EAX
                                   sum = 202
2-26


               고생길
• 해결 방법은?
  – Data Race가 있으면 멀티 쓰레드 프로그램이
    의도하지 않은 결과를 낼 수 있다.
  – Data Race를 없애면 된다.
• 어떻게
  – Lock과 Unlock을 사용한다.
2-27


       고생길
• 결과
2-28


                      고생길
• 결과가 옳게 나왔다. 만족하는가?
            실행시간      결과
 1 Thread   280577    100000000
 2 Thread   146823    50876664
 4 Thread   132362    27366758

            No LOCK                     실행시간      결과
                             1 Thread   2888071   100000000
                             2 Thread   5947291   100000000
                             4 Thread   4606754   100000000
     35배의 성능차이
                                        With LOCK
2-29


               고생길
• EnterCriticalSection() 이라는 물건은
  – 한번에 하나의 쓰레드만 실행 시킴
  – Lock을 얻지 못하면 시스템 호출
2-30


                    고생길
• 해결 방법은?
  – Lock을 쓰지 않으면 된다.
  – “Sum += 2”를 Atomic하게 만들면 된다.




• Atomic
  – 실행 중 다른 Core가 끼어들지 못하도록 한다.
2-31


                        고생길
• 결과가 옳게 나왔다. 만족하는가?
           실행시간       결과
1 Thread   280577     100000000
                                              실행시간      결과
2 Thread   146823     50876664
                                   1 Thread   1001528   100000000
4 Thread   132362     27366758
                                   2 Thread   1462121   100000000
            No LOCK                4 Thread   1452311   100000000
           실행시간      결과
1 Thread   2888071   100000000
                                        With InterlockedOperation
2 Thread   5947291   100000000
4 Thread   4606754   100000000

                                 With LOCK
2-32


         HELL
• 정답은?
2-33


                                고생길
• 만족하는가? (i7 – 4core)
            실행시간         결과                     실행시간        결과
 1 Thread   280,577      100000000   1 Thread   2,888,071   100000000
 2 Thread   146,823      50876664    2 Thread   5,947,291   100000000
 4 Thread   132,362      27366758    4 Thread   4,606,754   100000000

            No LOCK                             With LOCK
            실행시간        결과                      실행시간        결과

 1 Thread   1,001,528   100000000    1 Thread   28,7776     100000000

 2 Thread   1,462,121   100000000    2 Thread   15,6394     100000000

 4 Thread   1,452,311   100000000    4 Thread   96,925      100000000


 With InterlockedOperation                           정답
2-34


                              고생길
• 만족하는가? (XEON E5405, 2CPU)
              실행시간      결과                     실행시간        결과
 1 Thread     1,798     100000000   1 Thread   27,073      100000000
 2 Thread     2,926     520251348   2 Thread   83,586      100000000
 4 Thread     1,692     203771150   4 Thread   69,264      100000000
 8 Thread     3,699     193307522   8 Thread   67,156      100000000
              No LOCK                          With LOCK
               실행시간     결과                     실행시간        결과
   1 Thread    11,307   100000000   1 Thread   2055        100000000
   2 Thread    24,194   100000000   2 Thread   1798        100000000
   4 Thread    20,292   100000000   4 Thread   834         100000000
   8 Thread    18,699   100000000   8 Thread   419         100000000

 With InterlockedOperation                            정답
2-35


                  고생길
• 지금까지
  – Visual Studio의 마수에서 벗어나기
    • Volatile을 잘 쓰자
  – 경쟁상태 해결하기.
    • Lock을 최소화 하자
    • Lock대신 atomic operation을 사용하자
2-36


                  고생길
• 그러나
  – 절대로 모든 문제가 정답처럼 풀리지 않는다.
  – Interlocked로 구현 가능하면 다행 (atomic)
    • Interlock이 가능한 것은 일부 Instruction
  – 일반적인 자료구조를 Lock없이 Atomic하게
    구현하는 것은 큰 문제다.
  – Lock말고도 다른 문제가 있다.
2-37


            HELL
• 멀티 코어에서는 Data Race말고도 다른
 문제점이 있다.

• “상상한 것 그 이상을 보여준다”, 충공깽
2-38


            HELL


EnterCriticalSection()이 문제가 있으니
나만의 Lock을 구현해 볼까?
2-39


                    HELL
• 다음의 프로그램으로 Lock과 Unlock이 동작할까?
  – 피터슨 알고리즘               volatile int victim = 0;
                           volatile bool flag[2] = {false, false};
  – 두개의 쓰레드에서 Lock구현
                           Lock(int threadId)
  – 운영체제 교과서에 실려 있음        {
                                     int i = threadId;
  – threadId는 0과 1이라고 가정             j = 1 – i;
                                     flag[i] = true;
• 실행해 보자                             victim = i;
                                     while(flag[j] &&
                                               victim == i) {}
• 결과는?                     }
                           Unlock (int threadId)
                           {
                                     int i = threadId;
                                     flag[i] = false;
                           }
2-40


         HELL
• 이유는?
2-41


                    HELL
• 이유는?
  – CPU는 사기를 친다.
   • Line Based Cache Sharing
   • Out of order execution
   • write buffering
 – CPU는 프로그램을 순차적으로 실행하는
   척만한다.
   • 싱글코어에서는 절대로 들키지 않는다.
2-42


                  HELL
• Out-of-order 실행
  a = fsin(b);
  f = 3;

  a = b;   // a,b는 cache miss
  c = d;   // c,d는 cache hit
2-43


              HELL
• 문제는 메모리
  – 프로그램 순서대로 읽고 쓰지 않는다.
   • 읽기와 쓰기는 시간이 많이 걸리므로.
   • 옆의 프로세서(core)에서 보면 보인다.
• 어떠한 일이 벌어지는가?
2-44


                           HELL
• 아래의 두 개의 실행결과는 서로 다르다 어떠한 것이
 정확한 결과인가?
  thread a                thread b
  write (x, 1)            write(x, 2)
                                                 Type-A
   read(x, 2)             read(x, 2)

                                 thread a                 thread b

                 Type-B           write (x, 1)            write(x, 2)

                                  read(x, 1)              read(x, 1)
2-45


                     HELL
• 그러면 이것은?
    thread a           thread b
    write (x, 1)      write(x, 2)    Type-C!!
    read(x, 2)        read(x, 1)


                      thread a              thread b
         Type-D !!    write (x, 1)         read(y, 1)
                      write (y, 1)         read(x, 0)
2-46


                          HELL
• 그러면 이것은?
         thread a               thread b
         write (x, 1)          write(y, 1)         Type-E

         read (y, 0)           Read(x, 0)


             thread a      thread b          thread c       thread d
Type-F
           write (x, 1)   write (x, 2)       read(x, 1)     read(x, 2)
                                             read(x, 2)     read(x, 1)
2-47


                HELL
• 현실
  – 앞의 여러 형태의 결과는 전부 가능하다.
• 부정확해 보이는 결과가 나오는 이유?
  – 현재의 CPU는 Out-of-order실행을 한다.
  – Cache가 프로세서마다 따로 존재 하며 Cache는 64byte
    line별로 따로 관리된다.
  – 위의 2가지가 없다면 Computer는 몇백배 느려진다.
2-48


                                         HELL
• 메모리 쓰기 순서는 모든 코어에서 일치 하는가?
#define THREAD_MAX 2                 int main()
#define SIZE 10000000                {
volatile int x,y;
                                        DWORD addr;
int trace_x[SIZE], trace_y[SIZE];
                                        HANDLE hThread[THREAD_MAX];
DWORD WINAPI ThreadFunc0(LPVOID a)
{                                        .. // Thread 2개 실행
   for(int i = 0; i <SIZE;i++) {
      x = i;                             int count = 0;
      trace_y[i] = y;
                                         for (int i=0; i< SIZE;++i)
    }
   return 0;                                if (trace_x[i] == trace_x[i+1])
}                                              if (trace_y[trace_x[i]] == trace_y[trace_x[i] + 1]) {
                                                  if (trace_y[trace_x[i]] != i) continue;
DWORD WINAPI ThreadFunc1(LPVOID a)                  printf ("Error x : %d, trace[x]%d --", i, trace_x[i]);
{                                                   printf ("y : %d : trace[y] : %d n",
   for(int i = 0; i <SIZE;i++) {
                                                           trace_x[i], trace_y[trace_x[i]]);
      y = i;
      trace_x[i] = x;                          }
    }                                          printf("Total Memory Inconsistency:%dn", count);
   return 0;                             return 0;
}                                    }
2-49


                                  HELL
• 프로그램 설명


                     2     7          6    1
 8보다 3이 먼저 write!!   3     7          7    2
                     4     8          8    2       3보다 8이 먼저 write!!!
                     5     8          9    3
                     6     8         10    5
                     7     9         11    6
                     x   traceY       y   traceX
2-50


HELL




       공황상태…
2-51


                                 HELL
• 메모리에는 유령이…
 bool done = false;
 volatile int *bound;
 int error;

 DWORD WINAPI ThreadFunc1(LPVOID lpVoid)
 {
           for (int j = 0; j<= 25000000; ++j)   *bound = -(1 + *bound);
           done = true;
           return 0;
 }

 DWORD WINAPI ThreadFunc2(LPVOID lpVOid)
 {
           while (!done) {
                     int v = *bound;
                     if ((v !=0) && (v != -1)) error ++;
           }
           return 0;
 }
2-52


                              HELL
• 어떻게 실행했길래?
volatile int ARR[256];


bound = (int *) ((((((int) ARR) + 128) >> 7 ) << 7) - 2) ;
error = 0;
DWORD addr;

HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc1, (LPVOID) 0, 0, &addr);
HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc2, (LPVOID) 1, 0, &addr);
2-53


             HELL
• 어떻게 실행했길래?
                          bound
                              2byte

     2byte




             Cache line
2-54


                          HELL
• 결과가….
 – 중간값
   • write시 최종값과 초기값이 아닌 다른 값이 도중에 메모리에 써지는
     현상                            short buf[256]

 – 이유는?                                    buf[0] = length;
                                           buf[1] = OP_MOVE;
    • Cache Line Size Boundary             *((float *)(&buf[2])) = x;
                                           *((float *)(&buf[4])) = y;
 – 대책은?                                    *((float *)(&buf[6])) = z;
                                           *((float *)(&buf[8])) = dx;
    • Pointer를 절대 믿지 마라.                   *((float *)(&buf[10])) = dy;
                                           *((float *)(&buf[12])) = dz;
    • Byte 밖에 믿을 수 없다.                     *((float *)(&buf[14])) = ax;
                                           *((float *)(&buf[16])) = ay;
    • Pointer가 아닌 변수는 Visual C++가 잘 해준다.   *((float *)(&buf[18])) = az;
                                           *((int *)(&buf[20])) = h;
                                           …
                                           send( fd, buf, (size_t)buf[0], 0 );
2-55


                      HELL
• 이러한 현상을 메모리 일관성(Memory Consistency)
 문제라고 부른다.




            http://en.wikipedia.org/wiki/Memory_ordering
2-56


                    HELL
• 어떻게 할 것인가?
 – 강제로 원하는 결과를 얻도록 한다.
    • 모든 메모리 접근을 Lock/Unlock으로 막으면 가능
      – 성능저하!!!
      – Lock은 어떻게 구현?
 – 위의 상황을 감안하고 프로그램 작성
    • 프로그래밍이 너무 어렵다.
      – 피터슨이나 빵집 알고리즘도 동작하지 않는다.


                어쩌라고???
2-57


                HELL
• 정리
  – 멀티쓰레드에서의 공유 메모리
   • 다른 코어에서 보았을 때 업데이트 순서가 틀릴 수 있다.
   • 메모리의 내용이 한 순간에 업데이트 되지 않을 때 도 있다.
 – 일반적인 프로그래밍 방식으로는 멀티쓰레드에서
  안정적으로 돌아가는 프로그램을 만들 수 없다.
2-58


                     내용
•   도입
•   현실
•   Welcome to the Hell.
•   새로운 희망
•   우리의 나아갈 길
•   실제성능
2-59


              희망
• 메모리에 대한 쓰기는 언젠가는 완료 된다.
• 자기 자신의 프로그램 순서는 지켜진다.
• 캐시의 일관성은 지켜진다.
  – 한번 지워졌던 값이 다시 살아나지는 않는다.
  – 언젠가는 모든 코어가 동일한 값을 본다
• 캐시라인 내부의 쓰기는 중간 값을 만들지 않는다.
2-60


                    희망
• 우리가 할 수 있는 것
  – CPU의 여러 삽질에도 불구 하고 Atomic Memory를 구현 할 수
    있다. 더군다나 Locking없이 SW적인 방법 만으로
  – 증명은 교재(아까 그 책) 참조
• Atomic
  – 접근(메모리는 read, write)의 절대 순서가 모든 쓰레드에서
    지켜지는 자료구조
  – 프로그래머가 필요로 하는 바로 그 자료구조
  – 싱글코어에서는 모든 메모리가 Atomic Memory이다.
2-61


                                 희망
•   Atomic Memory 만 있으면 되는가?
    – NO
    – 우리가 진짜로 필요로 하는 것은 멀티쓰레드에서 동작하는 효율적인
      자료구조들이다.
       • Queue, Stack, List, Map, Tree……
    – 즉 Atomic한 자료구조가 필요하다.
•   STL은???
    – 못 쓴다.
•   STL + LOCK은???
    – 느려서 못쓴다.
•   근데 “효율적인” 이라니?
2-62


          Lock없는 프로그램
• 효율적인 구현
  – Lock없는 구현
    • 성능 저하의 주범이므로 당연
  – Lock이 없다고 성능저하가 없는가??
    • 상대방 쓰레드에서 어떤 일을 해주기를 기다리는 한
      동시실행으로 인한 성능 개선을 얻기 힘들다.
      – while (other_thread.flag == true);
      – lock과 동일한 성능저하
    • 상대방 쓰레드의 행동에 의존적이지 않는 구현방식이 필요하다.
2-63


            Lock없는 프로그램
• 효율적인 구현
  – 블럭킹 (blocking)
     • 다른 쓰레드의 진행상태에 따라 진행이 막힐 수 있음
        – 예) while(lock != 0);
     • 멀티쓰레드의 bottle neck이 생긴다.
     • Lock을 사용하면 블럭킹
  – 넌블럭킹 (non-blocking)
     • 다른 쓰레드가 어떠한 삽질을 하고 있던 상관없이 진행
        – 예) 공유메모리 읽기/쓰기, Interlocked Operation
2-64


            Lock없는 프로그램
• 넌블럭킹의 등급
  – 무대기 (wait-free)
     • 모든 메소드가 정해진 유한한 단계에 실행을 끝마침
     • 멈춤 없는 프로그램 실행
  – 무잠금 (lock-free)
     •   무한히 많은 메소드가 유한한 단계에 실행을 끝마침
     •   무대기이면 무잠금이다
     •   기아(starvation)을 유발하기도 한다.
     •   성능을 위해 무대기 대신 무잠금을 선택하기도 한다.
2-65


          Lock없는 프로그램
• 넌블럭킹의 등급
  – 제한된 무대기 (bounded wait-free)
     • 유한하지만 쓰레드의 개수에 비례한 유한일 수 있다.
  – 무간섭 (obstruction-free)
     • 한 개를 제외한 모든 쓰레드가 멈추었을 때, 멈추지 않은
       쓰레드의 메소드가 유한한 단계에 종료할 때.
     • 충돌 중인 쓰레드를 잠깐 멈추게 할 필요가 있다.
2-66


           Lock없는 프로그램
• 정리
  – Wait-free, Lock-free
     • Lock을 사용하지 않고
     • 다른 쓰레드가 어떠한 행동을 하기를 기다리는 것
       없이
     • 자료구조의 접근을 Atomic하게 해주는 프로그램
2-67


        병행성과 정확성
• 그러면, Atomic Memory로 그런 자료구조를
 만들면 되지 않는가?

• Atomic Memory만으로는 다중 쓰레드
 무대기 큐를 만들 수 없다!!!!!!
 – (증명) : 아까 그 책
2-68


           병행성과 정확성
• 다중 쓰레드 무대기 큐를 만들려면?
  – 합의 (Consensus)객체가 필요하다.
    • 합의
      – 여러 개의 쓰레드 중 하나만 골라내어 그 선택자를 모두에게
        알려주는 API
      – 당연히 무대기
  – 실제 컴퓨터에 존재하는가?
    • 존재한다.
    • 특별한 CPU 명령으로 구현된다.
2-69


                합의(Consensus)
•   <넘어가자>
•   동기화 연산의 능력을 알아보는데 사용되는 알고리즘
•   합의 객체 의 정의 <PASS>
    – value_t decide(value_t value) 메소드를 구현
    – n 개의 스레드가 decide를 호출한다.
    – 하나의 스레드는 한번 이하로만 호출한다.
    – decide는 모든 호출에 대해 같은 값을 반환한다.
    – decde가 반환하는 값은 어떠한 스레드가 입력한 값이다.
2-70


                합의(Consensus)
• 구현 (Windows API)
  – InterlockedCompareExchange()
   LONG __cdecl InterlockedCompareExchange(
            __inout LONG volatile *Destination,
            __in LONG Exchange,
            __in LONG Comparand );

  – Destination에 저장된 값과 Comparand가 같은 값이면
    Destination에 Exchange를 쓴다.
  – Destination에 저장되어 있던 값을 리턴한다.
  – Atomic하다.
2-71


          합의(Consensus)
• 실제 x86 CPU상의 구현
  – lock prefix와 cmpxchg 명령어로 구현
  – lock cmpxchg [A], b 기계어 명령으로 구현
    • eax에 비교값, A에 주소, b에 넣을 값
• EnterCriticalSection()도 위와 같은 어셈블리를
 사용해서 구현되어 있다.
  – 하지만 lock을 얻지 못하면 Windows kernel 호출크리.
2-72


      합의(Consensus)
• 합의의 위용
  – 모든 자료구조를 멀티쓰레드 무대기자료구조로
    만들 수 있다.
  – 바꿔주는 프로그램이 있다.
  – STL도 OK!
2-73


                   합의(Consensus)
Response Apply(Invocation Invoc, int threadId) {
Node *prefer = new Node(Invoc);
while (prefer->seq == 0) {
   Node *before = tail.GetMax(head);
   LONG decide_prefer = reinterpret_cast<LONG>(prefer);
   Node *after = reinterpret_cast<Node*>( before->decideNext.Decide(decide_prefer) );
   before->next = after;
   after->seq = before->seq + 1;
   head[threadId] = after;
}
SeqQueue myObject;

Node *current = tail.next;
while (current != prefer) {
   myObject.Apply(current->invoc);
   current = current->next;
}
return myObject.Apply(current->invoc);
2-74


                  희망
• Happy End????
  – NO
• 합의를 가지고 자료구조를 <효율적인> 무대기나
 무잠금으로 구현하는 것은 매우 어려운 일이다.
  – 구현은 어렵지 않으나 성능은 안습이다.
성능 비교
•   네할렘 XEON, E5520, 2.3GHz, 2CPU (8 core)
•   STL의 queue를 무잠금, 무대기로 구현한 것과, CriticalSection으로
    atomic하게 만든것의 성능 비교.
    – Test조건 : 16374번 Enqueue, Dequeue (결과는 mili second)
    – EnterCriticaSection()을 사용한 것은 테스트 데이터의 크기가 128배
    – 따라서 50000배 성능 차이 (4개 thread의 경우)
               쓰레드 갯수           1      2      4      8      16
               무잠금 만능          5091   7445   6811   10004   6846
               무대기 만능          5021   7257   7026   9170    6466
               EnterCritical   222    238    219    254     257


• EnterCriticalSection을 사용해야 하는가?
    –   No : 멀티쓰레드에서의 성능향상이 없다.
2-76


               희망
• 결론
  – CPU가 제공하는 Consensus를 사용하면 모든
    싱글쓰레드 알고리즘을 Lock-free한 멀티쓰레드
    알고리즘으로 변환할 수 있다.
• 현실
  – 비효율 적이다.
2-77


                            희망
• 대안
  – 자료구조에 맞추어 최적화된 lock-free알고리즘을 일일이 개발해야
    한다.
     • 멀티쓰레드 프로그램은 힘들다. => 연봉이 높다.
• 다른 데서 구해 쓸 수도 있다.
  – Intel TBB, VS2010 PPL
  – 인터넷
  – 하지만 범용적일 수록 성능이 떨어진다. 자신에게 딱 맞는 것을
    만드는 것이 좋다.
2-78


                     내용
•   도입
•   현실
•   Welcome to the Hell.
•   새로운 희망
•   우리의 나아갈 길
•   실제성능
2-79


                    실제 성능
• 속도 비교 예제)
  – Set의 구현
   • 중복 불가
   • 정렬되어 있음
 – Add, Remove, Contains 메소드
 – 여러 가지 병렬화 기법 사용
   • Course, Fine, Optimistic, Fine, Lock-Free
 – 인용) 아까 그 책
2-80


             실제 성능
• Course Set          EnterCriticalSection(&lock);
                      pred = &head;
                      curr = pred->next;
  – 모든 메소드에 Locking
                      while (curr->key < key) {
                                pred = curr;
                                curr = curr->next;
                      }
                      if (key == curr->key) {
                                pred->next = curr->next;
                                delete curr;
                                LeaveCriticalSection(&lock);
                                return true;
                      } else {
                                LeaveCriticalSection(&lock);
                                return false;
                      }
2-81


              실제 성능
• Fine Set          head.Lock();
                    pred = &head;
                    curr = pred->next;
  – 수정이 필요한         curr->Lock();
                    while (curr->key < key) {
                              pred->Unlock();
    Node만 Locking             pred = curr;
                              curr = curr->next;
                              curr->Lock();
                    }
                    if (curr->key == key) {
                              pred->next = curr->next;
                              curr->Unlock();
                              pred->Unlock();
                              return true;
                    }
                    curr->Unlock();
                    pred->Unlock();
                    return false;
2-82


                   실제 성능
                    while (true) {
• Optimistic Set       Node *pred = &head;
                       Node *curr = pred->next;
                       while (curr->key < key) {
  – 수정이 필요한               pred = curr;
                          curr = curr->next;
    Node검색을            }
                       pred->Lock();    curr->Lock();
    Locking없이 하고       if (validate(pred, curr)) {
                          if (curr->key == key) {
  – Locking 후 확인             pred->next = curr->next;
                             pred->Unlock();    curr->Unlock();
                             // delete curr;
                             return true;
                          } else {
                             pred->Unlock();    curr->Unlock();
                             return false;
                          }
                       }
                       pred->Unlock();    curr->Unlock();
                    }
2-83


                실제 성능
• Lazy Set
                      while (true) {
                         Node *pred = &head;
                         Node *curr = head.next;
                         while (curr->key < key) {
  – 지운 노드를 직접               pred = curr;
                            curr = curr->next;
    지우지 않고               }
                         pred->Lock();    curr->Lock();

    marking만 한다.         if (validate(pred, curr)) {
                            if (curr->key != key) {
                                  curr->Unlock();   pred->Unlock();
  – Contain()에서                   return false;
                            } else {

    잠금을 없앰                        curr->marked = true;
                                  pred->next = curr->next;
                                  curr->Unlock(); pred->Unlock();
  – Optimistic의 최적화         }
                                  return true;

                            curr->Unlock(); pred->Unlock();
                         }
                         curr->Unlock();   pred->Unlock();
                      }
2-84


                    실제 성능
• Lock-free Set             bool snip;
                            while (true) {
  – Lock()사용안함                 Window *window = find(&head, key);
                               Node *pred = window->pred;
  – Next필드와 marked필드를          Node *curr = window->curr;
    동시에 atomic하게               if (curr->key != key) return false;
                               else {
    업데이트 하는 것을 구현                 Node *succ = GetReference(curr->next);
  – Pointer의 LSB를 marked로         snip = curr->attemptMark(succ, true);
                                  if (!snip) continue;
    사용                            pred->CompareAndSet(curr, succ, false,
                            false);
                                  return true;
                               }
                            }
2-85


                              실제 성능
• Lock-free Set
   – CompareAndSet의 구현
 bool CompareAndSet(Node *old_node, Node *next_node, bool old_mark, bool next_mark) {
    DWORD old_addr = reinterpret_cast<DWORD>(old_node);
    if (old_mark) old_addr = old_addr | 0x1;
    else old_addr = old_addr & 0xfffffffe;
    DWORD next_addr = reinterpret_cast<DWORD>(next_node);
    if (next_mark) next_addr = next_addr | 0x1;
    else next_addr = next_addr & 0xfffffffe;
    int prev_addr = InterlockedCompareExchange(reinterpret_cast<long *>(&next),
                                         next_addr, old_addr);
    return (prev_addr == old_addr);
 }
2-86


                   속도 비교
• Intel i7 920 (단위: 초)
   – 32767번 랜덤한 숫자 추가, 삭제 반복
 쓰레드 개수   Course   Fine    Optimistic Lazy   LockFree
    1     0.715    3.350   1.800     0.914   0.864
    2     0.992    2.723   1.267     0.668   0.589
    4     0.972    1.575   0.691     0.355   0.350
    8     0.970    1.199   0.463     0.278   0.247
    16    0.999    1.180   0.552     0.250   0.273
2-87


                실제 성능
• 우리의 목적
  – 고성능
  – 쉬운 프로그래밍
• 번역하면
  – 무대기 (wait-free)
  – 멀티쓰레드 자료구조 (Queue, Stack, List~~~)
2-88


                     정리
• 지향하는 프로그래밍 스타일
  – Lock을 사용한 프로그래밍
   • Blocking
   • 느림 (몇 백배)
 – Atomic Memory를 사용한 프로그래밍
   • 표현력이 떨어짐
   • Queue도 만들지 못함
 – 무대기 자료구조를 사용한 프로그래밍
   • OK
2-89


             정리
• 무대기(wait-free) 자료구조를 사용한
 프로그래밍
 – 각각의 자료구조를 Lock을 사용하지 않고
   구현해야 한다.
 – Lock은 사용하지 않지만 합의객체는 필요하다.
   (합의 객체 없이는 만들 수 없음이 증명되어
   있다.)
2-90


                            정리
•   멀티 스레드 프로그래밍은 공유 메모리를 사용해서 스레드간의 협업을
    한다.
•   공유메모리를 사용한 동기화는 사용하기 힘들다.
    – 일관성, 중간 값
    – Atomic memory의 한계
•   공유 자료 구조를 만들어서 사용하는 것이 좋다.
•   좋은 공유 자료 구조는 만들기 힘들다.
    – 무대기(wait free)알고리즘의 작성은 까다롭다.
    – 상용 라이브러리도 좋다. Intel TBB, VS2010 PPL(Parallel Patterns
      Library)등
2-91


                            미래
• 그래도 멀티쓰레딩은 힘들다.
 – 멀티쓰레드 프로그래머 연봉이 높은 이유
• Core가 늘어나면 지금 까지의 방법도 한계
 – lock-free. wait-free overhead증가
 – interlocked operation overhead증가
• 예측
 – Transactional Memory
 – 새로운 언어의 필요
    • 예) Earlang, Haskell
2-92


                                                 TIP
•   Nehalem으로 업그레이드 하라.
     –   좋다! QPI만세
     –   Opteron을 쓸지언정 절대 구형 XEON은 쓰지 말아라.
•   Lock을 쓸 때 Spinlock은 TTS을 사용하고 일정 회전 이후는 Sleep()으로 쓰레드를 재워라.
     –   RWLock(Reader Write Lock)을 사용할 수 있으면 하라.
•   수정 후에는 반드시 Testing
     –   Lock이 허름한 무대기/무잠금보다 성능이 높은 경우가 많다.
•   메모리관리는 따로 해라
     –   tsmalloc(thread cashing malloc)도 괜찮다.
•   인텔 매뉴얼을 믿지 마라.
     –   CPU 실행방식 변경가능, CPU가 Arm으로 바뀔 수도 있다.
     –   매뉴얼 믿고 쓰다가 뒤통수 (너무 고약하다)
     –   자주 바뀌는 매뉴얼
•   Worst Case 튜닝
     –   저부하 일때 CPU낭비 상관 없음
•   Wait-free는 Lock-free에서 starvation을 제거한 것
2-93


                   NEXT
• 다음 강의(내년???)
  – Lock-free search : SKIP-LIST
  – 효율적인 LOCK : tts, back-off, spin-count 고찰
  – Next Solution
     • Stateless 프로그래밍 언어
     • Transactional Memory
  – ABA Problem, aka 효율적인 reference counting
2-94


                                    Q&A
•   연락처
    – nhjung@kpu.ac.kr
    – <제작중> http://wsl.kpu.ac.kr/~gameserver/index.html
    – 발표자료 : ftp://210.93.61.41 id:ndc21 passwd: <바람의나라>
•   참고자료
    – Herlihy, Shavit, “The Art of Multiprocesor Programming”, Morgan Kaufman, 2008
    – SEWELL, P., SARKAR, S., OWENS, S., NARDELLI, F. Z., AND MYREEN, M. O.
      x86-tso: A rigorous and usable programmer’s model for x86 multiprocessors.
      Communications of the ACM 53, 7 (July 2010), 89–97.
    – INTEL, “Intel 64 and IA-32 Architectures Software Developer’s Manual”, Vol 3A:
      System Programming Guide, Part 1
2-95


                    광고
• 체계적으로 배우고 싶으신 분
  – 한국산업기술대학교 게임공학과 대학원
  – 회사를 설득해서 계약 체결
   •   JCE와 계약 경험(win-win)
   •   회사로 출장 강의
   •   등록금 공짜 (정부지원)
   •   석사학위 취득

More Related Content

What's hot

임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012
devCAT Studio, NEXON
 
임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013
devCAT Studio, NEXON
 
리플렉션과 가비지 컬렉션
리플렉션과 가비지 컬렉션리플렉션과 가비지 컬렉션
리플렉션과 가비지 컬렉션
QooJuice
 
Windows system - memory개념잡기
Windows system - memory개념잡기Windows system - memory개념잡기
Windows system - memory개념잡기
ChangKyu Song
 
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
devCAT Studio, NEXON
 
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
devCAT Studio, NEXON
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
devCAT Studio, NEXON
 

What's hot (20)

임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012임태현, MMO 서버 개발 포스트 모템, NDC2012
임태현, MMO 서버 개발 포스트 모템, NDC2012
 
임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013임태현, 게임 서버 디자인 가이드, NDC2013
임태현, 게임 서버 디자인 가이드, NDC2013
 
NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현NDC12_Lockless게임서버설계와구현
NDC12_Lockless게임서버설계와구현
 
Tips and experience of DX12 Engine development .
Tips and experience of DX12 Engine development .Tips and experience of DX12 Engine development .
Tips and experience of DX12 Engine development .
 
C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현
 
리플렉션과 가비지 컬렉션
리플렉션과 가비지 컬렉션리플렉션과 가비지 컬렉션
리플렉션과 가비지 컬렉션
 
게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가게임서버프로그래밍 #8 - 성능 평가
게임서버프로그래밍 #8 - 성능 평가
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
 
Multiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theoremMultiplayer Game Sync Techniques through CAP theorem
Multiplayer Game Sync Techniques through CAP theorem
 
[NDC 2009] 행동 트리로 구현하는 인공지능
[NDC 2009] 행동 트리로 구현하는 인공지능[NDC 2009] 행동 트리로 구현하는 인공지능
[NDC 2009] 행동 트리로 구현하는 인공지능
 
Windows system - memory개념잡기
Windows system - memory개념잡기Windows system - memory개념잡기
Windows system - memory개념잡기
 
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
 
[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기
 
게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP Adv게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP Adv
 
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
 
나만의 엔진 개발하기
나만의 엔진 개발하기나만의 엔진 개발하기
나만의 엔진 개발하기
 
언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.
 
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
윤석주, 신입 게임 프로그래머가 되는 법 - 넥슨 채용 프로세스 단계별 분석, NDC2019
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
 
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...
 

Viewers also liked

[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
Jaeseung Ha
 
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
SukYun Yoon
 
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
흥배 최
 

Viewers also liked (20)

시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
시즌 2: 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
multi-thread 어플리케이션에 대해 모든 개발자가 알아 두지 않으면 안 되는 것
multi-thread 어플리케이션에 대해 모든 개발자가 알아 두지 않으면 안 되는 것multi-thread 어플리케이션에 대해 모든 개발자가 알아 두지 않으면 안 되는 것
multi-thread 어플리케이션에 대해 모든 개발자가 알아 두지 않으면 안 되는 것
 
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
 
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
게임서버프로그래밍 #4 - 멀티스레드 프로그래밍
 
20150212 c++11 features used in crow
20150212 c++11 features used in crow20150212 c++11 features used in crow
20150212 c++11 features used in crow
 
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
[NDC2015] 언제 어디서나 프로파일링 가능한 코드네임 JYP 작성기 - 라이브 게임 배포 후에도 프로파일링 하기
 
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
프로그래머가 몰랐던 멀티코어 CPU 이야기 13, 14장
 
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
 
Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8
 
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
KGC 2016 오픈소스 네트워크 엔진 Super socket 사용하기
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games ConferenceKGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
KGC 2016: HTTPS 로 모바일 게임 서버 구축한다는 것 - Korea Games Conference
 
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
 
Iocp 기본 구조 이해
Iocp 기본 구조 이해Iocp 기본 구조 이해
Iocp 기본 구조 이해
 
게임서버프로그래밍 #1 - IOCP
게임서버프로그래밍 #1 - IOCP게임서버프로그래밍 #1 - IOCP
게임서버프로그래밍 #1 - IOCP
 
Windows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCPWindows Registered I/O (RIO) vs IOCP
Windows Registered I/O (RIO) vs IOCP
 
게임서버프로그래밍 #5 - 데이터베이스 핸들링
게임서버프로그래밍 #5 - 데이터베이스 핸들링게임서버프로그래밍 #5 - 데이터베이스 핸들링
게임서버프로그래밍 #5 - 데이터베이스 핸들링
 
게임서버프로그래밍 #6 - 예외처리 및 로깅
게임서버프로그래밍 #6 - 예외처리 및 로깅게임서버프로그래밍 #6 - 예외처리 및 로깅
게임서버프로그래밍 #6 - 예외처리 및 로깅
 
게임서버프로그래밍 #3 - 메모리 및 오브젝트 풀링
게임서버프로그래밍 #3 - 메모리 및 오브젝트 풀링게임서버프로그래밍 #3 - 메모리 및 오브젝트 풀링
게임서버프로그래밍 #3 - 메모리 및 오브젝트 풀링
 
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
 

Similar to Ndc12 2

242 naver-2
242 naver-2242 naver-2
242 naver-2
NAVER D2
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9
진현 조
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기
진현 조
 
Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900
Samsung Electronics
 
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
Kiheon Park
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
Esun Kim
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요
KTH
 
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
KTH, 케이티하이텔
 

Similar to Ndc12 2 (20)

242 naver-2
242 naver-2242 naver-2
242 naver-2
 
MutiCore 19-20
MutiCore 19-20MutiCore 19-20
MutiCore 19-20
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9
 
ARTIK 710 IoT class 02
ARTIK 710 IoT class 02ARTIK 710 IoT class 02
ARTIK 710 IoT class 02
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스
 
190821 delphi
190821 delphi190821 delphi
190821 delphi
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기
 
ffmpeg optimization using CUDA
ffmpeg optimization using CUDAffmpeg optimization using CUDA
ffmpeg optimization using CUDA
 
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing SystemGCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
 
Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900
 
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
 
5 swift 기초함수
5 swift 기초함수5 swift 기초함수
5 swift 기초함수
 
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
 
2011 H3 컨퍼런스-파이썬으로 클라우드 하고 싶어요
2011 H3 컨퍼런스-파이썬으로 클라우드 하고 싶어요2011 H3 컨퍼런스-파이썬으로 클라우드 하고 싶어요
2011 H3 컨퍼런스-파이썬으로 클라우드 하고 싶어요
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요
 
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
H3 2011 파이썬으로 클라우드 하고 싶어요_분산기술Lab_하용호
 
Swift의 함수와 메소드
Swift의 함수와 메소드Swift의 함수와 메소드
Swift의 함수와 메소드
 

Ndc12 2

  • 1. 멀티쓰레드 프로그래밍이 왜이리 힘드나요? (컴파일러와 하드웨어에서 Lock-free 알고리즘 까지) 정내훈 한국산업기술대학교 게임공학과
  • 2. 2-2 발표자 소개 • KAIST 전산과 박사 – 전공 : 멀티프로세서 CPU용 메모리 일관성 유지 HW • NCSoft 근무 – Alterlife , Project M 프로그램 팀장 • 현 : 한국산업기술대학교 게임공학과 부교수 – 학부 강의 : 게임서버프로그래밍 – 대학원 강의 : 멀티코어프로그래밍
  • 3. 2-3 목차 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길 • 실제성능
  • 4. 2-4 도입 • 멀티쓰레드 프로그래밍 이란? – 멀티코어 혹은 멀티프로세서 컴퓨터의 성능을 이끌어 내기 위한 프로그래밍 기법 – 흑마술의 일종 • 잘못 사용하면 패가 망신
  • 5. 2-5 도입 • 흑마술 멀티쓰레드 프로그래밍의 위험성 – “자꾸 죽는데 이유를 모르겠어요” • 자매품 : “이상한 값이 나오는데 이유를 모르겠어요” – “더 느려져요”
  • 6. 2-6 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 7. 2-7 현실 • “멀티쓰레드 안 해도 되지 않나요?” – NO! – “MultiThread 프로그래밍을 하지 않는 이상 프로그램의 성능은 전혀 나아지지 않을 것임” – by Intel, AMD • “공짜 점심은 끝났어요~~”
  • 8. 2-8 현실 피할 곳도 숨을 곳도 없습니다.
  • 9. 2-9 현실 • 멀티쓰레드 프로그래밍을 하지 않으면? – (멀티코어) CPU가 놀아요. – 경쟁회사 제품보다 느려요. • FPS • 동접
  • 10. 2-10 현실 • 멀티 코어 CPU가 왜 나왔는가? – 예전에는 만들기 힘들어서? No – 다른 방법들의 약발이 다 떨어져서! • 클럭 속도, 캐시, 슈퍼스칼라, Out-of-order, 동적 분기 예측… – 늦게 나온 이유 • 프로그래머에게 욕을 먹을 것이 뻔하기 때문.
  • 11. 2-11 현실 • 컴퓨터 공학을 전공했지만 학부에서 가르치지 않았다. • 큰맘 먹고 스터디를 시작했지만 한 달도 못 가서 흐지부지 되었다. (원인은 다음 페이지) • 그냥 멀티쓰레드 안 쓰기로 했다.
  • 12. 2-12 현실 • 좋은 교재
  • 13. 2-13 현실 • 왜 멀티쓰레드 프로그래밍이 어려운가? – 다른 쓰레드의 영향을 고려해서 프로그램 해야 하기 때문에 – 에러 재현과 디버깅이 힘들어서 – Visual Studio가 사기를 치고 있기 때문 • 왜 멀티쓰레드 프로그래밍이 진짜로 어려운가? – CPU가 사기를 치고 있기 때문
  • 14. 2-14 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 15. 2-15 고생길 • Visual Studio의 사기 DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { data = 1; flag = true; } DWORD WINAPI ThreadFunc2(LPVOID lpVoid) { while(!flag); my_data = data; }
  • 16. 2-16 고생길 • Visual Studio의 사기 DWORD WINAPI ThreadFunc2(LPVOID lpVOid) { DWORD WINAPI ThreadFunc2(LPVOID lpVoid) 00951020 mov al,byte ptr [flag (953374h)] { while(!flag); while (!flag); my_data = data; 00951025 test al,al 00951027 je ThreadFunc2+5 (951025h) } int my_data = data; printf("Data is %Xn", my_data); 00951029 mov eax,dword ptr [data (953370h)] 0095102E push eax 0095102F push offset string "Data is %Xn" (952104h) 00951034 call dword ptr [__imp__printf 싱글 쓰레드 프로그램이면? (9520ACh)] 0095103A add esp,8 VS는 무죄! return 0; 0095103D xor eax,eax } 0095103F ret 4
  • 17. 2-17 고생길 • Visual Studio의 사기를 피하는 방법 – volatile을 사용하면 된다. • 반드시 메모리를 읽고 쓴다. • 변수를 레지스터에 할당하지 않는다. • 읽고 쓰는 순서를 지킨다. – 참 쉽죠? – “어셈블리를 모르면 Visual Studio의 사기를 알 수 없다” 흠좀무…
  • 18. 2-18 고생길 • 정말 쉬운가??? struct Qnode { volatile int data; volatile Qnode* next; }; 무엇이 문제일까?? DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { while ( qnode->next == NULL ) { } my_data = qnode->next->data; }
  • 19. 고생길 • volatile의 사용법 – volatile int * a; • *a = 1; // 순서를 지킴 • a = b; // 순서를 지키지 않는다. – int * volatile a; • *a = 1; // 순서를 지키지 않음, • a = b; // 이것은 순서를 지킴
  • 20. 고생길 • Volatile 위치 오류의 예 volatile Qnode* next; Qnode * volatile next; void UnLock() { Qnode *qnode; qnode = myNode; if (qnode->next == NULL) { LONG long_qnode = reinterpret_cast<LONG>(qnode); volatile LONG *long_tail = reinterpret_cast<volatile LONG*>(&tail); if ( CAS(long_tail, NULL, long_qnode) ) return; while ( qnode->next == NULL ) { } } qnode->next->locked = false; qnode->next = NULL; 011F1089 mov eax,dword ptr [esi+4] } 011F108C lea esp,[esp] 011F1090 cmp eax,ebx 011F1092 je ThreadFunc+90h (11F1090h) 01191090 mov eax,dword ptr [esi+4] 01191093 test eax,eax 01191095 je ThreadFunc+90h (1191090h)
  • 21. 2-21 고생길 • Thread 2개로 합계 1억을 만드는 프로그램 #include <windows.h> #include <stdio.h> volatile int sum = 0; DWORD WINAPI ThreadFunc(LPVOID lpVoid) { for (int i=1;i<=25000000;i++) sum += 2; return 0; } int main() { DWORD addr; HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &addr); HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &addr); WaitForSingleObject(hThread2, INFINITE); WaitForSingleObject(hThread3, INFINITE); CloseHandle(hThread2); CloseHandle(hThread3); printf(“Result is %dn", sum); getchar(); return 0; }
  • 22. 2-22 고생길 • 다중 쓰레드 - 결과
  • 23. 2-23 고생길 • 결과는? • 엉뚱한 답
  • 24. 2-24 고생길 • 왜 틀린 결과가 나왔을까? ─ “sum+=2”가 문제이다.
  • 25. 2-25 고생길 • 왜 틀린 결과가 나왔을까? – DATA RACE (복수의 쓰레드에서 같은 공유 메모리에 동시에 WRITE하려는 행위.) 때문이다. 쓰레드 1 쓰레드 2 MOV EAX, SUM sum = 200 ADD EAX, 2 MOV EAX, SUM sum = 200 MOV SUM, EAX ADD EAX, 2 sum = 202 MOV SUM, EAX sum = 202
  • 26. 2-26 고생길 • 해결 방법은? – Data Race가 있으면 멀티 쓰레드 프로그램이 의도하지 않은 결과를 낼 수 있다. – Data Race를 없애면 된다. • 어떻게 – Lock과 Unlock을 사용한다.
  • 27. 2-27 고생길 • 결과
  • 28. 2-28 고생길 • 결과가 옳게 나왔다. 만족하는가? 실행시간 결과 1 Thread 280577 100000000 2 Thread 146823 50876664 4 Thread 132362 27366758 No LOCK 실행시간 결과 1 Thread 2888071 100000000 2 Thread 5947291 100000000 4 Thread 4606754 100000000 35배의 성능차이 With LOCK
  • 29. 2-29 고생길 • EnterCriticalSection() 이라는 물건은 – 한번에 하나의 쓰레드만 실행 시킴 – Lock을 얻지 못하면 시스템 호출
  • 30. 2-30 고생길 • 해결 방법은? – Lock을 쓰지 않으면 된다. – “Sum += 2”를 Atomic하게 만들면 된다. • Atomic – 실행 중 다른 Core가 끼어들지 못하도록 한다.
  • 31. 2-31 고생길 • 결과가 옳게 나왔다. 만족하는가? 실행시간 결과 1 Thread 280577 100000000 실행시간 결과 2 Thread 146823 50876664 1 Thread 1001528 100000000 4 Thread 132362 27366758 2 Thread 1462121 100000000 No LOCK 4 Thread 1452311 100000000 실행시간 결과 1 Thread 2888071 100000000 With InterlockedOperation 2 Thread 5947291 100000000 4 Thread 4606754 100000000 With LOCK
  • 32. 2-32 HELL • 정답은?
  • 33. 2-33 고생길 • 만족하는가? (i7 – 4core) 실행시간 결과 실행시간 결과 1 Thread 280,577 100000000 1 Thread 2,888,071 100000000 2 Thread 146,823 50876664 2 Thread 5,947,291 100000000 4 Thread 132,362 27366758 4 Thread 4,606,754 100000000 No LOCK With LOCK 실행시간 결과 실행시간 결과 1 Thread 1,001,528 100000000 1 Thread 28,7776 100000000 2 Thread 1,462,121 100000000 2 Thread 15,6394 100000000 4 Thread 1,452,311 100000000 4 Thread 96,925 100000000 With InterlockedOperation 정답
  • 34. 2-34 고생길 • 만족하는가? (XEON E5405, 2CPU) 실행시간 결과 실행시간 결과 1 Thread 1,798 100000000 1 Thread 27,073 100000000 2 Thread 2,926 520251348 2 Thread 83,586 100000000 4 Thread 1,692 203771150 4 Thread 69,264 100000000 8 Thread 3,699 193307522 8 Thread 67,156 100000000 No LOCK With LOCK 실행시간 결과 실행시간 결과 1 Thread 11,307 100000000 1 Thread 2055 100000000 2 Thread 24,194 100000000 2 Thread 1798 100000000 4 Thread 20,292 100000000 4 Thread 834 100000000 8 Thread 18,699 100000000 8 Thread 419 100000000 With InterlockedOperation 정답
  • 35. 2-35 고생길 • 지금까지 – Visual Studio의 마수에서 벗어나기 • Volatile을 잘 쓰자 – 경쟁상태 해결하기. • Lock을 최소화 하자 • Lock대신 atomic operation을 사용하자
  • 36. 2-36 고생길 • 그러나 – 절대로 모든 문제가 정답처럼 풀리지 않는다. – Interlocked로 구현 가능하면 다행 (atomic) • Interlock이 가능한 것은 일부 Instruction – 일반적인 자료구조를 Lock없이 Atomic하게 구현하는 것은 큰 문제다. – Lock말고도 다른 문제가 있다.
  • 37. 2-37 HELL • 멀티 코어에서는 Data Race말고도 다른 문제점이 있다. • “상상한 것 그 이상을 보여준다”, 충공깽
  • 38. 2-38 HELL EnterCriticalSection()이 문제가 있으니 나만의 Lock을 구현해 볼까?
  • 39. 2-39 HELL • 다음의 프로그램으로 Lock과 Unlock이 동작할까? – 피터슨 알고리즘 volatile int victim = 0; volatile bool flag[2] = {false, false}; – 두개의 쓰레드에서 Lock구현 Lock(int threadId) – 운영체제 교과서에 실려 있음 { int i = threadId; – threadId는 0과 1이라고 가정 j = 1 – i; flag[i] = true; • 실행해 보자 victim = i; while(flag[j] && victim == i) {} • 결과는? } Unlock (int threadId) { int i = threadId; flag[i] = false; }
  • 40. 2-40 HELL • 이유는?
  • 41. 2-41 HELL • 이유는? – CPU는 사기를 친다. • Line Based Cache Sharing • Out of order execution • write buffering – CPU는 프로그램을 순차적으로 실행하는 척만한다. • 싱글코어에서는 절대로 들키지 않는다.
  • 42. 2-42 HELL • Out-of-order 실행 a = fsin(b); f = 3; a = b; // a,b는 cache miss c = d; // c,d는 cache hit
  • 43. 2-43 HELL • 문제는 메모리 – 프로그램 순서대로 읽고 쓰지 않는다. • 읽기와 쓰기는 시간이 많이 걸리므로. • 옆의 프로세서(core)에서 보면 보인다. • 어떠한 일이 벌어지는가?
  • 44. 2-44 HELL • 아래의 두 개의 실행결과는 서로 다르다 어떠한 것이 정확한 결과인가? thread a thread b write (x, 1) write(x, 2) Type-A read(x, 2) read(x, 2) thread a thread b Type-B write (x, 1) write(x, 2) read(x, 1) read(x, 1)
  • 45. 2-45 HELL • 그러면 이것은? thread a thread b write (x, 1) write(x, 2) Type-C!! read(x, 2) read(x, 1) thread a thread b Type-D !! write (x, 1) read(y, 1) write (y, 1) read(x, 0)
  • 46. 2-46 HELL • 그러면 이것은? thread a thread b write (x, 1) write(y, 1) Type-E read (y, 0) Read(x, 0) thread a thread b thread c thread d Type-F write (x, 1) write (x, 2) read(x, 1) read(x, 2) read(x, 2) read(x, 1)
  • 47. 2-47 HELL • 현실 – 앞의 여러 형태의 결과는 전부 가능하다. • 부정확해 보이는 결과가 나오는 이유? – 현재의 CPU는 Out-of-order실행을 한다. – Cache가 프로세서마다 따로 존재 하며 Cache는 64byte line별로 따로 관리된다. – 위의 2가지가 없다면 Computer는 몇백배 느려진다.
  • 48. 2-48 HELL • 메모리 쓰기 순서는 모든 코어에서 일치 하는가? #define THREAD_MAX 2 int main() #define SIZE 10000000 { volatile int x,y; DWORD addr; int trace_x[SIZE], trace_y[SIZE]; HANDLE hThread[THREAD_MAX]; DWORD WINAPI ThreadFunc0(LPVOID a) { .. // Thread 2개 실행 for(int i = 0; i <SIZE;i++) { x = i; int count = 0; trace_y[i] = y; for (int i=0; i< SIZE;++i) } return 0; if (trace_x[i] == trace_x[i+1]) } if (trace_y[trace_x[i]] == trace_y[trace_x[i] + 1]) { if (trace_y[trace_x[i]] != i) continue; DWORD WINAPI ThreadFunc1(LPVOID a) printf ("Error x : %d, trace[x]%d --", i, trace_x[i]); { printf ("y : %d : trace[y] : %d n", for(int i = 0; i <SIZE;i++) { trace_x[i], trace_y[trace_x[i]]); y = i; trace_x[i] = x; } } printf("Total Memory Inconsistency:%dn", count); return 0; return 0; } }
  • 49. 2-49 HELL • 프로그램 설명 2 7 6 1 8보다 3이 먼저 write!! 3 7 7 2 4 8 8 2 3보다 8이 먼저 write!!! 5 8 9 3 6 8 10 5 7 9 11 6 x traceY y traceX
  • 50. 2-50 HELL 공황상태…
  • 51. 2-51 HELL • 메모리에는 유령이… bool done = false; volatile int *bound; int error; DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { for (int j = 0; j<= 25000000; ++j) *bound = -(1 + *bound); done = true; return 0; } DWORD WINAPI ThreadFunc2(LPVOID lpVOid) { while (!done) { int v = *bound; if ((v !=0) && (v != -1)) error ++; } return 0; }
  • 52. 2-52 HELL • 어떻게 실행했길래? volatile int ARR[256]; bound = (int *) ((((((int) ARR) + 128) >> 7 ) << 7) - 2) ; error = 0; DWORD addr; HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc1, (LPVOID) 0, 0, &addr); HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc2, (LPVOID) 1, 0, &addr);
  • 53. 2-53 HELL • 어떻게 실행했길래? bound 2byte 2byte Cache line
  • 54. 2-54 HELL • 결과가…. – 중간값 • write시 최종값과 초기값이 아닌 다른 값이 도중에 메모리에 써지는 현상 short buf[256] – 이유는? buf[0] = length; buf[1] = OP_MOVE; • Cache Line Size Boundary *((float *)(&buf[2])) = x; *((float *)(&buf[4])) = y; – 대책은? *((float *)(&buf[6])) = z; *((float *)(&buf[8])) = dx; • Pointer를 절대 믿지 마라. *((float *)(&buf[10])) = dy; *((float *)(&buf[12])) = dz; • Byte 밖에 믿을 수 없다. *((float *)(&buf[14])) = ax; *((float *)(&buf[16])) = ay; • Pointer가 아닌 변수는 Visual C++가 잘 해준다. *((float *)(&buf[18])) = az; *((int *)(&buf[20])) = h; … send( fd, buf, (size_t)buf[0], 0 );
  • 55. 2-55 HELL • 이러한 현상을 메모리 일관성(Memory Consistency) 문제라고 부른다. http://en.wikipedia.org/wiki/Memory_ordering
  • 56. 2-56 HELL • 어떻게 할 것인가? – 강제로 원하는 결과를 얻도록 한다. • 모든 메모리 접근을 Lock/Unlock으로 막으면 가능 – 성능저하!!! – Lock은 어떻게 구현? – 위의 상황을 감안하고 프로그램 작성 • 프로그래밍이 너무 어렵다. – 피터슨이나 빵집 알고리즘도 동작하지 않는다. 어쩌라고???
  • 57. 2-57 HELL • 정리 – 멀티쓰레드에서의 공유 메모리 • 다른 코어에서 보았을 때 업데이트 순서가 틀릴 수 있다. • 메모리의 내용이 한 순간에 업데이트 되지 않을 때 도 있다. – 일반적인 프로그래밍 방식으로는 멀티쓰레드에서 안정적으로 돌아가는 프로그램을 만들 수 없다.
  • 58. 2-58 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길 • 실제성능
  • 59. 2-59 희망 • 메모리에 대한 쓰기는 언젠가는 완료 된다. • 자기 자신의 프로그램 순서는 지켜진다. • 캐시의 일관성은 지켜진다. – 한번 지워졌던 값이 다시 살아나지는 않는다. – 언젠가는 모든 코어가 동일한 값을 본다 • 캐시라인 내부의 쓰기는 중간 값을 만들지 않는다.
  • 60. 2-60 희망 • 우리가 할 수 있는 것 – CPU의 여러 삽질에도 불구 하고 Atomic Memory를 구현 할 수 있다. 더군다나 Locking없이 SW적인 방법 만으로 – 증명은 교재(아까 그 책) 참조 • Atomic – 접근(메모리는 read, write)의 절대 순서가 모든 쓰레드에서 지켜지는 자료구조 – 프로그래머가 필요로 하는 바로 그 자료구조 – 싱글코어에서는 모든 메모리가 Atomic Memory이다.
  • 61. 2-61 희망 • Atomic Memory 만 있으면 되는가? – NO – 우리가 진짜로 필요로 하는 것은 멀티쓰레드에서 동작하는 효율적인 자료구조들이다. • Queue, Stack, List, Map, Tree…… – 즉 Atomic한 자료구조가 필요하다. • STL은??? – 못 쓴다. • STL + LOCK은??? – 느려서 못쓴다. • 근데 “효율적인” 이라니?
  • 62. 2-62 Lock없는 프로그램 • 효율적인 구현 – Lock없는 구현 • 성능 저하의 주범이므로 당연 – Lock이 없다고 성능저하가 없는가?? • 상대방 쓰레드에서 어떤 일을 해주기를 기다리는 한 동시실행으로 인한 성능 개선을 얻기 힘들다. – while (other_thread.flag == true); – lock과 동일한 성능저하 • 상대방 쓰레드의 행동에 의존적이지 않는 구현방식이 필요하다.
  • 63. 2-63 Lock없는 프로그램 • 효율적인 구현 – 블럭킹 (blocking) • 다른 쓰레드의 진행상태에 따라 진행이 막힐 수 있음 – 예) while(lock != 0); • 멀티쓰레드의 bottle neck이 생긴다. • Lock을 사용하면 블럭킹 – 넌블럭킹 (non-blocking) • 다른 쓰레드가 어떠한 삽질을 하고 있던 상관없이 진행 – 예) 공유메모리 읽기/쓰기, Interlocked Operation
  • 64. 2-64 Lock없는 프로그램 • 넌블럭킹의 등급 – 무대기 (wait-free) • 모든 메소드가 정해진 유한한 단계에 실행을 끝마침 • 멈춤 없는 프로그램 실행 – 무잠금 (lock-free) • 무한히 많은 메소드가 유한한 단계에 실행을 끝마침 • 무대기이면 무잠금이다 • 기아(starvation)을 유발하기도 한다. • 성능을 위해 무대기 대신 무잠금을 선택하기도 한다.
  • 65. 2-65 Lock없는 프로그램 • 넌블럭킹의 등급 – 제한된 무대기 (bounded wait-free) • 유한하지만 쓰레드의 개수에 비례한 유한일 수 있다. – 무간섭 (obstruction-free) • 한 개를 제외한 모든 쓰레드가 멈추었을 때, 멈추지 않은 쓰레드의 메소드가 유한한 단계에 종료할 때. • 충돌 중인 쓰레드를 잠깐 멈추게 할 필요가 있다.
  • 66. 2-66 Lock없는 프로그램 • 정리 – Wait-free, Lock-free • Lock을 사용하지 않고 • 다른 쓰레드가 어떠한 행동을 하기를 기다리는 것 없이 • 자료구조의 접근을 Atomic하게 해주는 프로그램
  • 67. 2-67 병행성과 정확성 • 그러면, Atomic Memory로 그런 자료구조를 만들면 되지 않는가? • Atomic Memory만으로는 다중 쓰레드 무대기 큐를 만들 수 없다!!!!!! – (증명) : 아까 그 책
  • 68. 2-68 병행성과 정확성 • 다중 쓰레드 무대기 큐를 만들려면? – 합의 (Consensus)객체가 필요하다. • 합의 – 여러 개의 쓰레드 중 하나만 골라내어 그 선택자를 모두에게 알려주는 API – 당연히 무대기 – 실제 컴퓨터에 존재하는가? • 존재한다. • 특별한 CPU 명령으로 구현된다.
  • 69. 2-69 합의(Consensus) • <넘어가자> • 동기화 연산의 능력을 알아보는데 사용되는 알고리즘 • 합의 객체 의 정의 <PASS> – value_t decide(value_t value) 메소드를 구현 – n 개의 스레드가 decide를 호출한다. – 하나의 스레드는 한번 이하로만 호출한다. – decide는 모든 호출에 대해 같은 값을 반환한다. – decde가 반환하는 값은 어떠한 스레드가 입력한 값이다.
  • 70. 2-70 합의(Consensus) • 구현 (Windows API) – InterlockedCompareExchange() LONG __cdecl InterlockedCompareExchange( __inout LONG volatile *Destination, __in LONG Exchange, __in LONG Comparand ); – Destination에 저장된 값과 Comparand가 같은 값이면 Destination에 Exchange를 쓴다. – Destination에 저장되어 있던 값을 리턴한다. – Atomic하다.
  • 71. 2-71 합의(Consensus) • 실제 x86 CPU상의 구현 – lock prefix와 cmpxchg 명령어로 구현 – lock cmpxchg [A], b 기계어 명령으로 구현 • eax에 비교값, A에 주소, b에 넣을 값 • EnterCriticalSection()도 위와 같은 어셈블리를 사용해서 구현되어 있다. – 하지만 lock을 얻지 못하면 Windows kernel 호출크리.
  • 72. 2-72 합의(Consensus) • 합의의 위용 – 모든 자료구조를 멀티쓰레드 무대기자료구조로 만들 수 있다. – 바꿔주는 프로그램이 있다. – STL도 OK!
  • 73. 2-73 합의(Consensus) Response Apply(Invocation Invoc, int threadId) { Node *prefer = new Node(Invoc); while (prefer->seq == 0) { Node *before = tail.GetMax(head); LONG decide_prefer = reinterpret_cast<LONG>(prefer); Node *after = reinterpret_cast<Node*>( before->decideNext.Decide(decide_prefer) ); before->next = after; after->seq = before->seq + 1; head[threadId] = after; } SeqQueue myObject; Node *current = tail.next; while (current != prefer) { myObject.Apply(current->invoc); current = current->next; } return myObject.Apply(current->invoc);
  • 74. 2-74 희망 • Happy End???? – NO • 합의를 가지고 자료구조를 <효율적인> 무대기나 무잠금으로 구현하는 것은 매우 어려운 일이다. – 구현은 어렵지 않으나 성능은 안습이다.
  • 75. 성능 비교 • 네할렘 XEON, E5520, 2.3GHz, 2CPU (8 core) • STL의 queue를 무잠금, 무대기로 구현한 것과, CriticalSection으로 atomic하게 만든것의 성능 비교. – Test조건 : 16374번 Enqueue, Dequeue (결과는 mili second) – EnterCriticaSection()을 사용한 것은 테스트 데이터의 크기가 128배 – 따라서 50000배 성능 차이 (4개 thread의 경우) 쓰레드 갯수 1 2 4 8 16 무잠금 만능 5091 7445 6811 10004 6846 무대기 만능 5021 7257 7026 9170 6466 EnterCritical 222 238 219 254 257 • EnterCriticalSection을 사용해야 하는가? – No : 멀티쓰레드에서의 성능향상이 없다.
  • 76. 2-76 희망 • 결론 – CPU가 제공하는 Consensus를 사용하면 모든 싱글쓰레드 알고리즘을 Lock-free한 멀티쓰레드 알고리즘으로 변환할 수 있다. • 현실 – 비효율 적이다.
  • 77. 2-77 희망 • 대안 – 자료구조에 맞추어 최적화된 lock-free알고리즘을 일일이 개발해야 한다. • 멀티쓰레드 프로그램은 힘들다. => 연봉이 높다. • 다른 데서 구해 쓸 수도 있다. – Intel TBB, VS2010 PPL – 인터넷 – 하지만 범용적일 수록 성능이 떨어진다. 자신에게 딱 맞는 것을 만드는 것이 좋다.
  • 78. 2-78 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길 • 실제성능
  • 79. 2-79 실제 성능 • 속도 비교 예제) – Set의 구현 • 중복 불가 • 정렬되어 있음 – Add, Remove, Contains 메소드 – 여러 가지 병렬화 기법 사용 • Course, Fine, Optimistic, Fine, Lock-Free – 인용) 아까 그 책
  • 80. 2-80 실제 성능 • Course Set EnterCriticalSection(&lock); pred = &head; curr = pred->next; – 모든 메소드에 Locking while (curr->key < key) { pred = curr; curr = curr->next; } if (key == curr->key) { pred->next = curr->next; delete curr; LeaveCriticalSection(&lock); return true; } else { LeaveCriticalSection(&lock); return false; }
  • 81. 2-81 실제 성능 • Fine Set head.Lock(); pred = &head; curr = pred->next; – 수정이 필요한 curr->Lock(); while (curr->key < key) { pred->Unlock(); Node만 Locking pred = curr; curr = curr->next; curr->Lock(); } if (curr->key == key) { pred->next = curr->next; curr->Unlock(); pred->Unlock(); return true; } curr->Unlock(); pred->Unlock(); return false;
  • 82. 2-82 실제 성능 while (true) { • Optimistic Set Node *pred = &head; Node *curr = pred->next; while (curr->key < key) { – 수정이 필요한 pred = curr; curr = curr->next; Node검색을 } pred->Lock(); curr->Lock(); Locking없이 하고 if (validate(pred, curr)) { if (curr->key == key) { – Locking 후 확인 pred->next = curr->next; pred->Unlock(); curr->Unlock(); // delete curr; return true; } else { pred->Unlock(); curr->Unlock(); return false; } } pred->Unlock(); curr->Unlock(); }
  • 83. 2-83 실제 성능 • Lazy Set while (true) { Node *pred = &head; Node *curr = head.next; while (curr->key < key) { – 지운 노드를 직접 pred = curr; curr = curr->next; 지우지 않고 } pred->Lock(); curr->Lock(); marking만 한다. if (validate(pred, curr)) { if (curr->key != key) { curr->Unlock(); pred->Unlock(); – Contain()에서 return false; } else { 잠금을 없앰 curr->marked = true; pred->next = curr->next; curr->Unlock(); pred->Unlock(); – Optimistic의 최적화 } return true; curr->Unlock(); pred->Unlock(); } curr->Unlock(); pred->Unlock(); }
  • 84. 2-84 실제 성능 • Lock-free Set bool snip; while (true) { – Lock()사용안함 Window *window = find(&head, key); Node *pred = window->pred; – Next필드와 marked필드를 Node *curr = window->curr; 동시에 atomic하게 if (curr->key != key) return false; else { 업데이트 하는 것을 구현 Node *succ = GetReference(curr->next); – Pointer의 LSB를 marked로 snip = curr->attemptMark(succ, true); if (!snip) continue; 사용 pred->CompareAndSet(curr, succ, false, false); return true; } }
  • 85. 2-85 실제 성능 • Lock-free Set – CompareAndSet의 구현 bool CompareAndSet(Node *old_node, Node *next_node, bool old_mark, bool next_mark) { DWORD old_addr = reinterpret_cast<DWORD>(old_node); if (old_mark) old_addr = old_addr | 0x1; else old_addr = old_addr & 0xfffffffe; DWORD next_addr = reinterpret_cast<DWORD>(next_node); if (next_mark) next_addr = next_addr | 0x1; else next_addr = next_addr & 0xfffffffe; int prev_addr = InterlockedCompareExchange(reinterpret_cast<long *>(&next), next_addr, old_addr); return (prev_addr == old_addr); }
  • 86. 2-86 속도 비교 • Intel i7 920 (단위: 초) – 32767번 랜덤한 숫자 추가, 삭제 반복 쓰레드 개수 Course Fine Optimistic Lazy LockFree 1 0.715 3.350 1.800 0.914 0.864 2 0.992 2.723 1.267 0.668 0.589 4 0.972 1.575 0.691 0.355 0.350 8 0.970 1.199 0.463 0.278 0.247 16 0.999 1.180 0.552 0.250 0.273
  • 87. 2-87 실제 성능 • 우리의 목적 – 고성능 – 쉬운 프로그래밍 • 번역하면 – 무대기 (wait-free) – 멀티쓰레드 자료구조 (Queue, Stack, List~~~)
  • 88. 2-88 정리 • 지향하는 프로그래밍 스타일 – Lock을 사용한 프로그래밍 • Blocking • 느림 (몇 백배) – Atomic Memory를 사용한 프로그래밍 • 표현력이 떨어짐 • Queue도 만들지 못함 – 무대기 자료구조를 사용한 프로그래밍 • OK
  • 89. 2-89 정리 • 무대기(wait-free) 자료구조를 사용한 프로그래밍 – 각각의 자료구조를 Lock을 사용하지 않고 구현해야 한다. – Lock은 사용하지 않지만 합의객체는 필요하다. (합의 객체 없이는 만들 수 없음이 증명되어 있다.)
  • 90. 2-90 정리 • 멀티 스레드 프로그래밍은 공유 메모리를 사용해서 스레드간의 협업을 한다. • 공유메모리를 사용한 동기화는 사용하기 힘들다. – 일관성, 중간 값 – Atomic memory의 한계 • 공유 자료 구조를 만들어서 사용하는 것이 좋다. • 좋은 공유 자료 구조는 만들기 힘들다. – 무대기(wait free)알고리즘의 작성은 까다롭다. – 상용 라이브러리도 좋다. Intel TBB, VS2010 PPL(Parallel Patterns Library)등
  • 91. 2-91 미래 • 그래도 멀티쓰레딩은 힘들다. – 멀티쓰레드 프로그래머 연봉이 높은 이유 • Core가 늘어나면 지금 까지의 방법도 한계 – lock-free. wait-free overhead증가 – interlocked operation overhead증가 • 예측 – Transactional Memory – 새로운 언어의 필요 • 예) Earlang, Haskell
  • 92. 2-92 TIP • Nehalem으로 업그레이드 하라. – 좋다! QPI만세 – Opteron을 쓸지언정 절대 구형 XEON은 쓰지 말아라. • Lock을 쓸 때 Spinlock은 TTS을 사용하고 일정 회전 이후는 Sleep()으로 쓰레드를 재워라. – RWLock(Reader Write Lock)을 사용할 수 있으면 하라. • 수정 후에는 반드시 Testing – Lock이 허름한 무대기/무잠금보다 성능이 높은 경우가 많다. • 메모리관리는 따로 해라 – tsmalloc(thread cashing malloc)도 괜찮다. • 인텔 매뉴얼을 믿지 마라. – CPU 실행방식 변경가능, CPU가 Arm으로 바뀔 수도 있다. – 매뉴얼 믿고 쓰다가 뒤통수 (너무 고약하다) – 자주 바뀌는 매뉴얼 • Worst Case 튜닝 – 저부하 일때 CPU낭비 상관 없음 • Wait-free는 Lock-free에서 starvation을 제거한 것
  • 93. 2-93 NEXT • 다음 강의(내년???) – Lock-free search : SKIP-LIST – 효율적인 LOCK : tts, back-off, spin-count 고찰 – Next Solution • Stateless 프로그래밍 언어 • Transactional Memory – ABA Problem, aka 효율적인 reference counting
  • 94. 2-94 Q&A • 연락처 – nhjung@kpu.ac.kr – <제작중> http://wsl.kpu.ac.kr/~gameserver/index.html – 발표자료 : ftp://210.93.61.41 id:ndc21 passwd: <바람의나라> • 참고자료 – Herlihy, Shavit, “The Art of Multiprocesor Programming”, Morgan Kaufman, 2008 – SEWELL, P., SARKAR, S., OWENS, S., NARDELLI, F. Z., AND MYREEN, M. O. x86-tso: A rigorous and usable programmer’s model for x86 multiprocessors. Communications of the ACM 53, 7 (July 2010), 89–97. – INTEL, “Intel 64 and IA-32 Architectures Software Developer’s Manual”, Vol 3A: System Programming Guide, Part 1
  • 95. 2-95 광고 • 체계적으로 배우고 싶으신 분 – 한국산업기술대학교 게임공학과 대학원 – 회사를 설득해서 계약 체결 • JCE와 계약 경험(win-win) • 회사로 출장 강의 • 등록금 공짜 (정부지원) • 석사학위 취득