SlideShare une entreprise Scribd logo
1  sur  104
Télécharger pour lire hors ligne
멀티쓰레드 프로그래밍이
왜이리 힘드나요?
(컴파일러와 하드웨어에서 Lock-free
알고리즘 까지)
정내훈
한국산업기술대학교 게임공학과
2-2

발표자 소개
• KAIST 전산과 박사
– 전공 : 멀티프로세서 CPU용 일관성 유지 HW
• NCSoft 근무
– Alterlife 프로그램 팀장
– Project M 프로그램 팀장
– CTO 직속 게임기술연구팀

• 현 : 한국산업기술대학교 게임공학과 부교수
– 학부 강의 : 게임서버프로그래밍
– 대학원 강의 : 멀티코어프로그래밍, 심화 게임서버
프로그래밍
2-3

참고
• NDC2012, KGC2012, CJE&M에서 강연한

내용
– 업데이트

• 삼성첨단기술연수소에서 강의한 내용
– 40시간 강의 (실습 포함) 의 앞부분
• 대학원 처음 4주 강의 분량의 압축
2-4

목차
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길
2-5

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

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

– “더 느려져요”

[미] MuliThreadProgramming [mʌ́ ltiθred-|proʊgrӕmɪŋ] : 1. 흑마술, 마공
2. 위력이 강대하나 다루기 어려워 잘 쓰이지 않는 기술
2-7

내용
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길
2-8

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

현실

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

현실
• 멀티쓰레드 프로그래밍을 하지 않으면?
– (멀티코어) CPU가 놀아요.
– 경쟁회사 제품보다 느려요.
• FPS(Frames Per Second)
• 동접
– 점점 줄어드는 사용자당 수입
– 만일 중국에 출동하면??
2-11

현실
• 멀티 코어 CPU가 왜 나왔는가?
– 예전에는 만들기 힘들어서? No
– 다른 방법들의 약발이 다 떨어져서!
• 클럭 속도, 캐시, 슈퍼스칼라, Out-of-order, 동적 분기
예측…

– 늦게 나온 이유
• 프로그래머에게 욕을 먹을 것이 뻔하기 때문.
– 기존 프로그램의 성능향상이 전혀 없고, 멀티 쓰레드
프로그래밍이 너무 어려워서.
2-12

현실
• 컴퓨터 공학을 전공했지만 학부에서

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

현실
• 좋은 교재
2-14

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

내용
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길
2-16

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

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

고생길
• Visual Studio의 사기
– 참조 <simple_sync>
DWORD WINAPI ThreadFunc2(LPVOID lpVoid)
{
DWORD WINAPI ThreadFunc2(LPVOID lpVOid)
while(!flag);
{
my_data = data;
00951020 mov
al,byte ptr [flag
}
00951025
00951027

싱글 쓰레드 프로그램이면?
VS는 무죄!

(953374h)]

while (!flag);
test
al,al
je
ThreadFunc2+5 (951025h)
int my_data = data;
printf("Data is %Xn", my_data);
mov
eax,dword ptr [data (953370h)]
push
eax
push
offset string "Data is %Xn"

00951029
0095102E
0095102F
(952104h)
00951034 call
(9520ACh)]
0095103A add
return 0;
0095103D xor
}
0095103F ret

dword ptr [__imp__printf
esp,8
eax,eax
4
2-18

고생길
• Visual Studio의 사기를 피하는 방법
– volatile을 사용하면 된다.
• 최적화를 하지 않는다.
• 반드시 메모리를 읽고 쓴다.
• 읽고 쓰는 순서를 지킨다.

– 참 쉽죠?

– “어셈블리를 모르면 Visual Studio의 사기를 알

수 없다” 흠좀무…
2-19

고생길
• 정말 쉬운가???
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
011F108C
011F1090
011F1092

01191090
01191093
01191095

mov
test
je

eax,dword ptr [esi+4]
eax,eax
ThreadFunc+90h (1191090h)

mov
lea
cmp
je

eax,dword ptr [esi+4]
esp,[esp]
eax,ebx
ThreadFunc+90h (11F1090h)
2-22

고생길
• 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-23

고생길
• Thread 2개로 합계 1억을 만드는 프로그램의 최신

유행
2-24

고생길
• 결과는?

• 엉뚱한 답
2-25

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

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

고생길
• 왜 틀린 결과가 나왔을까?
– DATA RACE (복수의 쓰레드에서 같은 공유
메모리에 WRITE하는 행위) 때문.
– “sum+=2”가 문제이다.
쓰레드 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-28

고생길
• 하지만

ADD SUM, 2
• 이 출동하면?
• 왜????
– 대학 2학년 때 컴퓨터구조와 운영체제시간에
배움
2-29

고생길
• Data Race의 해결 방법은?
– Data Race를 없애면 된다.
• 어떻게
– Lock과 Unlock을 사용한다.
– Windows에서는 EnterCriticalSection(),
LeaveCriticalSection()
– Linux에서는 pthread_mutex_lock(),
pthread_mutex_un
– C++11에서는 std::mutex의 lock(), unlock()
2-30

고생길
• 결과
2-31

고생길
• 결과가 옳게 나왔다. 만족하는가?
실행시간

결과

1 Thread

280,577

100000000

2 Thread

146,823

50876664

4 Thread

132,362

27366758

No LOCK
실행시간
1 Thread

2,888,071

100000000

2 Thread

35배의 성능차이

결과

5,947,291

100000000

4 Thread

4,606,754

100000000

With LOCK
2-32

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

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

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

고생길
• 결과가 옳게 나왔다. 만족하는가?
실행시간

결과

1 Thread

280,577

100000000

2 Thread

146,823

50876664

4 Thread

132,362

27366758

실행시간

결과

1 Thread

1,001,528

100000000

2 Thread

1,462,121

100000000

4 Thread

1,452,311

결과

1 Thread

No LOCK

실행시간
2,888,071

100000000

2 Thread

5,947,291

100000000

4 Thread

4,606,754

100000000

100000000

With LOCK

With InterlockedOperation
2-35

고생길
• 정답은?
2-36

고생길
• 만족하는가? (i7 – 4core)
실행시간

결과

1 Thread

280,577

100000000

2 Thread

146,823

4 Thread

132,362

실행시간

결과

1 Thread

2,888,071

100000000

50876664

2 Thread

5,947,291

100000000

27366758

4 Thread

4,606,754

100000000

No LOCK

With LOCK

실행시간

결과

실행시간

결과

1 Thread

1,001,528

100000000

1 Thread

287,776

100000000

2 Thread

1,462,121

100000000

2 Thread

156,394

100000000

4 Thread

1,452,311

100000000

4 Thread

96,925

100000000

With InterlockedOperation

정답
2-37

고생길
• 만족하는가? (XEON E5405, 2CPU)
실행시간

결과

1 Thread

1,798

100000000

2 Thread

2,926

4 Thread
8 Thread

실행시간

결과

1 Thread

27,073

100000000

520251348

2 Thread

83,586

100000000

1,692

203771150

4 Thread

69,264

100000000

3,699

193307522

8 Thread

67,156

100000000

No LOCK

With LOCK

실행시간

결과

1 Thread

11,307

100000000

2 Thread

24,194

4 Thread
8 Thread

실행시간

결과

1 Thread

2055

100000000

100000000

2 Thread

1798

100000000

20,292

100000000

4 Thread

834

100000000

18,699

100000000

8 Thread

419

100000000

With InterlockedOperation

정답
2-38

고생길
• 만족하는가? (XEON E5-4620, 4CPU, 32Core)
실행
시간

결과

1 Thread

0.425

100000000

2 Thread

0.678

4 Thread

실행
시간

결과

1 Thread

1.703

100000000

56567966

2 Thread

13.21

100000000

0.768

27254540

4 Thread

11.45

100000000

8 Thread

1.009

16257652

8 Thread

32.27

100000000

16Thread

0.942

14320406

16Thread

46.10

100000000

32Thread

1.706

8570996

32Thread

80.76

100000000

64Thread

1.926

3855910

64Thread

80.32

100000000

No LOCK

결과

1 Thread

0.877

100000000

2 Thread

3.344

4 Thread

실행
시간

결과

1 Thread

0.422

100000000

100000000

2 Thread

0.328

100000000

2.653

100000000

4 Thread

0.168

100000000

8 Thread

Interlocked
Operation

실행
시간

With LOCK

2.515

100000000

8 Thread

0.080

100000000

16Thread

2.624

100000000

16Thread

0.080

100000000

32Thread

3.353

100000000

32Thread

0.043

100000000

64Thread

3.061

100000000

64Thread

0.028

100000000

정답
2-39

고생길
• 지금까지
– Visual Studio의 마수에서 벗어나기
• Volatile을 잘 쓰자

– 경쟁상태 해결하기.
• Lock을 최소화 하자
• Lock대신 atomic operation을 사용하자
2-40

고생길
• 그러나
– 절대로 모든 문제가 정답처럼 풀리지 않는다.
– Interlocked로 구현 가능하면 다행 (atomic)
• Interlock이 가능한 것은 일부 Instruction

– 일반적인 자료구조를 Lock없이 Atomic하게

구현하는 것은 큰 문제다.
– Lock말고도 다른 문제가 있다.
2-41

HELL
• 멀티 코어에서는 Data Race말고도 다른

문제점이 있다.
• “상상한 것 그 이상을 보여준다”, 충공깽
2-42

HELL

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

HELL
• 다음의 프로그램으로 Lock과 Unlock이 동작할까?
– 피터슨 알고리즘
– 두 개의 쓰레드에서 Lock구현
– 운영체제 교과서에 실려 있음
– threadId는 0과 1이라고 가정 (myID에 저장)

• 실행해 보자
• 결과는?

volatile int victim = 0;
volatile bool flag[2] = {false, false};
Lock(int myID)
{
int other = 1 – myID;
flag[myID] = true;
victim = myID;
while(flag[other] && victim == myID) {}
}
Unlock (int myID)
{
flag[myID] = false;
}
2-44

HELL
• 결과는?
2-45

HELL
• 이유는?
– CPU는 사기를 친다.
• Line Based Cache Sharing
• Out of order execution
• write buffering

– CPU는 프로그램을 순차적으로 실행하는

척만한다.
• 자기 자신이 실행하는 프로그램에게는 제대로
실행하는 것처럼 거짓말한다.
• 옆의 Core에서 보면 거짓말이 보인다.
2-46

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

a = b;
c = d;

// a,b는 cache miss
// c,d는 cache hit
2-47

HELL
• 문제는 메모리
– 프로그램 순서대로 읽고 쓰지 않는다.
• 읽기와 쓰기는 시간이 많이 걸리므로.
• 옆의 프로세서(core)에서 보면 속도차와 실행순서
뒤바뀜이 보인다.

• 어떠한 일이 벌어지는가?
2-48

병행성과 정확성
• 아래의 두 개의 실행결과는 서로 다르다

어떠한 것이 정확한 결과인가?
thread a

thread b

write (x, 1)

write(x, 2)

read(x, 2)

Type-A

read(x, 2)

Type-B !!

thread a

thread b

write (x, 1)

read(y, 1)

write (y, 1)

read(x, 0)
2-49

병행성과 정확성
• 그러면 이것은?
thread a

thread b

write (x, 1)

write(x, 2)

read(x, 2)

Type-C!!

read(x, 1)

thread a

thread b

write (x, 1)

write(y, 1)

read (y, 0)

Read(x, 0)

Type-D!!
2-50

HELL
• 현실
– 앞의 여러 형태의 결과는 전부 가능하다.
• 부정확해 보이는 결과가 나오는 이유?
– 현재의 CPU는 Out-of-order실행을 한다.
– 메모리의 접근은 순간적이 아니다.
– 멀티 코어에서는 옆의 코어의 Out-of-order
실행이 관측된다.
2-51

HELL
• 진짜?

• 확인해 보자.
• 메모리 접근 순서를 강제로 맞추어 주는

명령어

_asm mfence;
• 앞에 피터슨 알고리즘에 적용해보자.
– 근데… 오류의 확률이 낮아서…
2-52

HELL
• 메모리 접근 오류 검출 프로그램을

사용해보자
• 아이디어.
– 메모리 내용을 계속 업데이트 하면서 다른

쓰레드의 업데이트를 같이 기록하여 나중에
기록된 로그를 비교해 보자.
2-53

HELL
• 정말 간단한 프로그램
#define THREAD_MAX 2
#define SIZE 10000000
volatile int x,y;
int trace_x[SIZE], trace_y[SIZE];
DWORD WINAPI ThreadFunc0(LPVOID a)
{
for(int i = 0; i <SIZE;i++) {
x = i;
trace_y[i] = y;
}
return 0;
}

int main()
{
DWORD addr;
HANDLE hThread[THREAD_MAX];

DWORD WINAPI ThreadFunc1(LPVOID a)
{
for(int i = 0; i <SIZE;i++) {
y = i;
trace_x[i] = x;
}
return 0;
}

.. // Thread 2개 실행
int count = 0;
for (int i=0; i< SIZE;++i)
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;
count++;
}
printf("Total Memory Inconsistency:%dn", count);
return 0;
}
2-54

HELL
• 프로그램 설명

8보다 3이 먼저 write!!

2
3
4
5
6
7

7
7
8
8
8
9

x

traceY

6
7
8
9
10
11
y

1
2
2
3
5
6
traceX

3보다 8이 먼저 write!!!
2-55

HELL

공황상태…
2-56

HELL
• 메모리 변경 순서가 뒤바뀔 확률은?
• _asm mfence를 넣어보자.
– 또는 C++11에서
#include <atomic>
std::atomic_thread_fence(std::memory_order_seq_cst);
2-57

병행성과 정확성
• 메모리에는 유령이
volatile bool done = false;
volatile int *bound;
int error;

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

*bound = -(1 + *bound);

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

병행성과 정확성
• 어떻게 실행했길래?
int ARR[32];
int temp = (int) &ARR[16];
temp = temp & 0xFFFFFFC0;
temp -= 2;
bound = (int *) temp;
*bound = 0;
HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc1, (LPVOID) 0, 0, &addr);
HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc2, (LPVOID) 1, 0, &addr);
2-59

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

2byte

Cache line
2-60

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

– 이유는?
• Cache Line Size Boundary

– 대책은?
• Pointer를 절대 믿지 마라.
• Byte 밖에 믿을 수 없다.
• Pointer가 아닌 변수는
– Visual C++ 또는 G++가 잘 해준다.

short buf[256]
buf[0] = length;
buf[1] = OP_MOVE;
*((float *)(&buf[2])) = x;
*((float *)(&buf[4])) = y;
*((float *)(&buf[6])) = z;
*((float *)(&buf[8])) = dx;
*((float *)(&buf[10])) = dy;
*((float *)(&buf[12])) = dz;
*((float *)(&buf[14])) = ax;
*((float *)(&buf[16])) = ay;
*((float *)(&buf[18])) = az;
*((int *)(&buf[20])) = h;
…
send( fd, buf, (size_t)buf[0], 0 );
어디서 많이 본 소스코드..
2-61

HELL
• 이러한 현상을 메모리 일관성(Memory

Consistency) 문제라고 부른다.
– x86은 얌전한 편, ARM CPU는 더하다.

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

HELL
• 정리
– 멀티쓰레드에서의 공유 메모리
• 다른 코어에서 보았을 때 업데이트 순서가 틀릴 수 있다.
• 메모리의 내용이 한 순간에 업데이트 되지 않을 때 도 있다.

– 일반적인 프로그래밍 방식으로는 멀티쓰레드에서

안정적으로 돌아가는 프로그램을 만들 수 없다.
2-63

HELL
• 어떻게 할 것인가?
– 위의 상황을 감안하고 프로그램 작성
• 프로그래밍이 너무 어렵다.
– 피터슨이나 빵집 알고리즘도 동작하지 않는다.

– 모든 공유메모리 접근을 Atomic하도록 수정한다.
• 모든 메모리 접근을 Lock/Unlock으로 막으면 가능
– 성능저하!!!, Lock은 어떻게 구현?

• Interlocked Operation 사용
– 간단한 연산만 가능, 성능저하

• mfence의 적절한 추가
– 적절하다는 보장은???

어쩌라고???
2-64

내용
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길
2-65

희망
• 언젠가는 메모리에 대한 쓰기가 실행 된다.

• 자기 자신의 프로그램 실행순서는 지켜진다.
• 캐시의 일관성은 지켜진다.
– 한번 지워졌던 값이 다시 살아나지는 않는다.
– 언젠가는 모든 코어가 동일한 값을 본다

• 캐시라인 내부의 쓰기는 중간 값을 만들지

않는다.
2-66

희망
• 우리가 할 수 있는 것
– CPU의 여러 삽질에도 불구 하고 주의 깊게
프로그래밍 하면 모든 메모리 접근을
Atomic하게 할 수 있다.
• HW의 도움 없이도 가능.
• 하지만 mfence가 효율적
2-67

희망
• Atomic Memory 만 있으면 되는가?
– NO
• 진짜 큰 규모의 상용 멀티쓰레드

프로그래밍은?
– 쓰레드간의 동기화나 자료 전송은 고유의

자료구조 사용
• Queue, Stack, List, Map, Tree……
• 예) Tera의 시야처리용 Lock-free job queue
• 예) Unreal3의 rendering command queue
2-68

희망
• 하지만.
– 지금까지 배운 모든 자료구조가
멀티쓰레드에서는 동작하지 않는다.
– STL도 동작하지 않는다.
– 다시 작성해야 한다.
• LOCK을 쓰면?
“Lock 없애야 해요 Lock 없앨 때 마다
동접이 300명씩 늘어났어요.” - N모사에서
L모 게임을 만들었던 S모님
2-69

Lock없는 프로그램
• 효율적인 구현
– Lock없는 구현
• 성능 저하의 주범이므로 당연
– Overhead & Critical Section
– Priority inversion
– Convoying

– Lock이 없다고 성능저하가 없는가??
• 상대방 쓰레드에서 어떤 일을 해주기를 기다리는 한
동시실행으로 인한 성능 개선을 얻기 힘들다.
– while (other_thread.flag == true);
– lock과 동일한 성능저하

• 상대방 쓰레드의 행동에 의존적이지 않는 구현방식이 필요하다.
2-70

Non-Blocking
• 블럭킹 (blocking)
– 다른 쓰레드의 진행상태에 따라 진행이 막힐 수
있음
• 예) while(lock != 0);

– 멀티쓰레드의 bottle neck이 생긴다.
– Lock을 사용하면 블럭킹

• 넌블럭킹 (non-blocking)
– 다른 쓰레드가 어떠한 삽질을 하고 있던
상관없이 진행
• 예) 공유메모리 읽기/쓰기, Interlocked Operation
2-71

Non-Blocking
• 블럭킹 알고리즘의 문제
– 성능저하
– Priority Inversion
• Lock을 공유하는 덜 중요한 작업들이 중요한 작업의 실행을
막는 현상
• Reader/Write Problem에서 많이 발생

– Convoying
• Lock을 얻은 쓰레드가 스케쥴링에서 제외된 경우, lock을
기다리는 모든 쓰레드가 공회전
• Core보다 많은 수의 thread를 생성했을 경우 자주 발생.

• 성능이 낮아도 Non-Blocking이 필요할 수 있다.
2-72

Non-Blocking
• 넌블럭킹의 등급
– 무대기 (wait-free)
• 모든 메소드가 정해진 유한한 단계에 실행을 끝마침
• 멈춤 없는 프로그램 실행

– 무잠금 (lock-free)
•
•
•
•

항상, 적어도 한 개의 메소드가 유한한 단계에 실행을 끝마침
무대기이면 무잠금이다
기아(starvation)을 유발하기도 한다.
성능을 위해 무대기 대신 무잠금을 선택하기도 한다.
2-73

Non-Blocking
• 정리
– Wait-free, Lock-free
• Lock을 사용하지 않고
• 다른 쓰레드가 어떠한 행동을 하기를 기다리는 것
없이
• 자료구조의 접근을 Atomic하게 해주는 알고리즘의
등급

– 멀티 쓰레드 프로그램에서 쓰레드 사이의

효율적인 자료 교환과 협업을 위해서는 NonBlocking 자료 구조가 필요하다.
2-74

병행성과 정확성
• 그러면, Atomic Memory로 그런 자료구조를

만들면 되지 않는가?
• Atomic Memory만으로는 다중 쓰레드

무대기 큐를 만들 수 없다!!!!!!
– (증명) : 아까 그 책
2-75

병행성과 정확성
• 다중 쓰레드 무대기 큐를 만들려면?
– CAS 명령어가 필요하다.
− CAS가 없이는 대부분의 non-blocking
알고리즘들을 구현할 수 없다.
• Queue, Stack, List…

− CAS를 사용하면 모든 싱글쓰레드 알고리즘

들을 Lock-free 알고리즘으로 변환할 수 있다!!!
− Lock-free 알고리즘의 핵심
2-76

CAS
• CAS
− CAS(&A, old, new);
− 의미 : 아래의 연산을 Atomic하게 수행

if (A == old) { A = new; return true; }
else return false;
− 다른 버전의 의미 : A메모리를 다른 쓰레드가 먼저

업데이트 해서 false가 나왔다. 모든 것을 포기하라.
2-77

CAS
• 구현 : Windows
– API
#include <windows.h>
LONG __cdecl InterlockedCompareExchange(
__inout LONG volatile *Destination,
__in LONG Exchange,
__in LONG Comparand );

– CAS의 구현
Bool CAS(LONG volatile *Addr, LONG New, LONG Old)
{
LONG temp = InterlockedCompareExchange(Addr, New, Old);
return temp == Old;
}
2-78

CAS
• 구현 : LINUX
#include <stdbool.h>
bool CAS(int *ptr, int oldval, int newval)
{
return __sync_bool_compare_and_swap(ptr, oldval, newval);
}
2-79

CAS
• 구현 : C++11
#include <atomic>
bool atomic_compare_exchange_strong( std::atomic<T>* obj,

T* expected, T desired );
2-80

CAS
• 실제 HW (x86 계열 CPU) 구현
– LOCK prefix와 CMPXCHG 명령어로 구현
– lock cmpxchg [A], b 기계어 명령으로
구현
• eax에 비교값, A에 주소, b에 넣을 값
if (eax == [a]) {
ZF = true;
[a] = b;
} else {
ZF = false;
eax = [a];
}
2-81

CAS
• 실제 HW (ARM) 구현
static inline AtomicWord CompareAndSwap(volatile AtomicWord* ptr,
AtomicWord old_value,
AtomicWord new_value)
{
uint32_t old, tmp;
__asm__ __volatile__("1: @ atomic cmpxchgn"
"mov %0, #0n"
"ldrex %1, [%2]n"
"teq %1, %3n"
"strexeq %0, %4, [%2]n"
"teq %0, #0n"
"bne 1b"
: "=&r" (tmp), "=&r" (old)
: "r" (ptr), "Ir" (old_value),
"r" (new_value)
: "cc");
return old;
}
2-82

CAS
• CAS의 위용
– 모든 자료구조를 멀티쓰레드 무대기자료구조로
만들 수 있다.
• 증명이 되어 있다.

– 바꿔주는 프로그램이 있다.
– STL도 OK!
2-83

CAS
•

모든 자료구조를 멀티쓰레드 Lock-Free로 바꿔주는 프로그램
class LFUniversal {
private:
Node *head[N], Node tail;
public:
LFUniversal() {
tail.seq = 1;
for (int i=0;i<N;++i) head[i] = &tail;
}
Response apply(Invocation invoc) {
int i = Thread_id();
Node prefer = Node(invoc);
while (prefer.seq == 0) {
Node *before = tail.max(head);
Node *after = before->decideNext->decide(&prefer);
before->next = after; after->seq = before->seq + 1;
head[i] = after;
}
SeqObject myObject;
Node *current = tail.next;
while (current != &prefer) {
myObject.apply(current->invoc);
current = current->next;
}
return myObject.apply(current->invoc);
} };
2-84

희망
• Happy End????
– NO
• 왜?
– 구현은 쉽다.
– 성능이 엉망이다.
이론 시간
• XEON, E5-4620, 2.2GHz, 4CPU (32 core)
• STL의 queue를 무잠금, 무대기로 구현한 것과,

CriticalSection으로 atomic하게 만든 것의 성능 비교.
– Test조건 : 16384번 Enqueue, Dequeue (결과는 mili second)
– EnterCriticaSection()을 사용한 것은 테스트 데이터의 크기가 100배
– 따라서 100배 성능 차이 (4개 thread의 경우)
쓰레드 갯수

1

2

4

8

16

32

64

무잠금 만능

3749

1966

1697

1120

742

525

413

무대기 만능

3640

1964

1219

1136

577

599

448

EnterCritical

232

822

1160

1765

1914

4803

7665

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

희망
• 결론
– CPU가 제공하는 CAS 명령어를 사용하면
기존의 모든 싱글쓰레드 알고리즘을 Lockfree한 멀티쓰레드 알고리즘으로 변환할 수
있다.
• 현실
– Universal Algorithm은 비효율 적이다.
2-87

희망
• 대안
– 자료구조에 맞추어 최적화된 lockfree알고리즘을 일일이 개발해야 한다.
• 멀티쓰레드 프로그램은 힘들다. => 연봉이 높다.

• 다른 데서 구해 쓸 수도 있다.
– Intel TBB, VS2012 PPL
– 인터넷
– 하지만 범용적일 수록 성능이 떨어진다.
자신에게 딱 맞는 것을 만드는 것이 좋다.
2-88

내용
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길
2-89

Non-Blocking
• 우리의 목적
– 정확한 결과
– 고성능
• 번역하면
– Lock을 사용하지 않고
– 비멈춤 (wait-free, lock-free)
– 자료구조 (Queue, Stack, List~~~)
2-90

Non-Blocking
• 지향하는 프로그래밍 스타일
– Lock을 사용한 프로그래밍
• Blocking
• 느림 (몇 백배)

– 원자적 레지스터를 사용한 프로그래밍
• 표현력이 떨어짐
• Queue도 만들지 못함
– Non-blocking 자료구조를 사용한 프로그래밍
• OK
2-91

내용
• 도입
• 현실
• Welcome to the Hell.

• 새로운 희망
• 우리의 나아갈 길 => 실제 예제
예제
• Non Blocking 자료 구조의 구현 법

• 예제 : 정렬된 링크드 리스트를 사용한 집합
– int를 집합에 add(), remove(), find()할 수 있는
자료 구조
• 성능 비교
리스트의 구현
• 1차 구현 : Lock의 사용
bool Remove(int key)
{
NODE *pred, *curr;
pred = &head;
EnterCriticalSection(&glock);
curr = pred->next;
while (curr->key < key) {
pred = curr; curr = curr->next;
}
if (key == curr->key) {
pred->next = curr->next;
delete curr;
LeaveCriticalSection(&glock);
return true;
} else {
LeaveCriticalSection(&glock);
return false;
}
}
리스트의 구현
• 2 차 구현 : Lock의 세밀화
bool Remove(int key)
{
NODE *pred, *curr;
head.lock();
pred = &head;
curr = pred->next;
curr->lock();
while (curr->key < key) {
pred->unlock();
pred = curr; curr = curr->next;
curr->lock();
}
if (key == curr->key) {
pred->next = curr->next;
curr->unlock();
pred->unlock();
delete curr;
return true;
} else {
curr->unlock();
pred->unlock(); return false;
}
}
리스트의 구현
•

3차 구현 : Lock감소

bool validate(NODE *pred, NODE *curr) {
NODE *node = &head;
while (node->key <= pred->key) {
if (node == pred) return pred->next == curr;
node = node->next;
}
return false;
}

bool Remove(int key)
{
NODE *pred, *curr;
while(true) {
pred = &head;
curr = pred->next;
while (curr->key < key) {
pred = curr;
curr = curr->next; }
pred->lock(); curr->lock();

if (!validate(pred, curr)) {
curr->unlock();
pred->unlock(); continue; }
if (key == curr->key) {
pred->next = curr->next;
curr->unlock();
pred->unlock();
// delete curr;
return true;
} else {
curr->unlock();
pred->unlock(); return false; }
}
}
리스트의 구현
• 4차구현 : 마킹 활용

bool validate(NODE *pred, NODE *curr) {
return (!pred->marked)
&& (!curr->marked)
&& (pred->next == curr);
}

bool Remove(int key)
{
NODE *pred, *curr;
while(true) {
pred = &head;
curr = pred->next;
while (curr->key < key) {
pred = curr;
curr = curr->next; }
pred->lock(); curr->lock();

if (!validate(pred, curr)) {
curr->unlock();
pred->unlock(); continue; }
if (key == curr->key) {
curr->marked = true;
pred->next = curr->next;
curr->unlock();
pred->unlock();
// delete curr;
return true;
} else {
curr->unlock();
pred->unlock(); return false; }
}
}
리스트의 구현
• 5차 구현 : Lock free
class LFNODE {
...
bool CompareAndSet(int old_v, int new_v)
{
int orig_v = InterlockedCompareExchange(reinterpret_cast<unsigned int *>(&next), new_v, old_v);
return orig_v == old_v;
}

bool CAS(LFNODE *old_node, LFNODE *new_node, bool oldMark, bool newMark) {
int oldvalue = reinterpret_cast<int>(old_node);
if (oldMark) oldvalue = oldvalue | 0x01;
else oldvalue = oldvalue & 0xFFFFFFFE;
int newvalue = reinterpret_cast<int>(new_node);
if (newMark) newvalue = newvalue | 0x01;
else newvalue = newvalue & 0xFFFFFFFE;
return CompareAndSet(oldvalue, newvalue);
}
bool AttemptMark(LFNODE *old_node, bool newMark) {
int oldvalue = reinterpret_cast<int>(old_node);
int newvalue = oldvalue;
if (newMark) newvalue = newvalue | 0x01;
else newvalue = newvalue & 0xFFFFFFFE;
return CompareAndSet(oldvalue, newvalue);
}
LFNODE *GetNextWithMark(bool *mark) {
int temp = reinterpret_cast<int>(next);
*mark = (0 != (temp & 0x01));
return reinterpret_cast<LFNODE *>(temp & 0xFFFFFFFE);
}

bool Remove(int key)
{
LFNODE *GetReference()
LFNODE *pred, *curr;
{
int temp = reinterpret_cast<int>(next);
return reinterpret_cast<LFNODE *>(temp & 0xFFFFFFFE);

void Find(LFNODE **Pred, LFNODE **Curr, int key)
{
LFNODE *pred = NULL;
LFNODE *curr = NULL;
LFNODE *succ = NULL;
bool marked = false;
ng_retry:
while(true) {
pred = &head;
curr = pred->GetReference();
while (true) {
succ = curr->GetNextWithMark(&marked);
while (marked) {
if (false == pred->CAS(curr, succ, false, false))
goto ng_retry;
curr = succ;
succ = curr->GetNextWithMark(&marked);
}
if (curr->key >= key) {
*Pred = pred; Curr = curr; return;
}
pred = curr; curr = succ;
}
}
}

while(true) {
Find(&pred, &curr, key);
LFNODE *AtomicMarkableReference(LFNODE *ptr, bool mark)
if (key != curr->key) return false;
{
int temp = reinterpret_cast<int>(ptr);
if (mark) temp = temp | 0x01;
LFNODE *succ = curr->GetReference();
else temp = temp & 0xFFFFFFFE;
return reinterpret_cast<LFNODE *>(temp); == curr->AttemptMark(succ, true)) continue;
if (false
}
pred->CAS(curr, succ, false, false);
return true;
}
}
}
};
2-98

속도 비교
• 1과 1000사이의 숫자의 랜덤한 4백만회

삽입/삭제/검색 (i7-920)
쓰레드 개수

1차

2차

3차

4차

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-99

정리
• 공유메모리를 사용한 동기화는 사용하기 힘들다.
– 일관성, 중간 값
– Atomic memory의 한계
• 공유 자료 구조를 사용해야 한다.
• 좋은 공유 자료 구조는 만들기 힘들다.
– Non-blocking 알고리즘의 작성은 까다롭다.
– 상용 라이브러리도 좋다. Intel TBB, VS2010
PPL(Parallel Patterns Library)등
– ??NOBEL library, Concurrency Kit
2-100

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

TIP
• 절대로 경험을 믿지 마라!!!
– 에러 날 확률이 로또 이하인 경우가 비일비재
– 디버깅 할 때, 사내 테스트 할 때는 멀쩡하다가
오픈베타 때 대형사고가 난다!!
– Correct가 증명된 알고리즘이나, 믿을 수 있는
회사에서 작성한 non-Blocking 프로그램을
사용하라.
• 자신이 만든 알고리즘이면 증명해봐라. (증명 방법은
교재 참조)
2-102

TIP
• 클라우드환경은 다르다.
– 많은 가상머신에서 CompareAndSwap
오퍼레이션의 딜레이가 급증하는 현상이 있다.
– Parallels on MaxOS-X (OK)
– VMWare, Parallels, VirtualBox on Windows-7
(성능저하)
2-103

NEXT
• 다음 발표(내년???)
– Lock-free 프로그래밍 근본적 이해
– 실제 MMO서버에서의 Lock-ree 성능 향상
– Transactional Memory with intel RTM
• 그 다음 발표???
− Lock-free search : SKIP-LIST
− ABA Problem, aka 효율적인 reference counting
− 고성능 MMO서버를 위한 non-blocking
자료구조의 활용
2-104

Q&A
• 연락처
– nhjung@kpu.ac.kr
– 발표자료 : ftp://210.93.61.41 id:ndc21 passwd: 바람의나라
• 또는 www.slideshare.net 에서 발표제목 검색

• 참고자료
– Herlihy, Shavit, “The Art of Multiprocesor Programming, revised”,

Morgan Kaufman, 2012
– 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

Contenu connexe

Tendances

GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception
GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with ExceptionGCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception
GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception상현 조
 
Multithread & shared_ptr
Multithread & shared_ptrMultithread & shared_ptr
Multithread & shared_ptr내훈 정
 
GCGC- CGCII 서버 엔진에 적용된 기술 (1)
GCGC- CGCII 서버 엔진에 적용된 기술 (1)GCGC- CGCII 서버 엔진에 적용된 기술 (1)
GCGC- CGCII 서버 엔진에 적용된 기술 (1)상현 조
 
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?강 민우
 
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012devCAT Studio, NEXON
 
UE4 Garbage Collection
UE4 Garbage CollectionUE4 Garbage Collection
UE4 Garbage CollectionQooJuice
 
Windows system - memory개념잡기
Windows system - memory개념잡기Windows system - memory개념잡기
Windows system - memory개념잡기ChangKyu Song
 
Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)Yoshifumi Kawai
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성NAVER D2
 
Modern C++의 타입 추론과 람다, 컨셉
Modern C++의 타입 추론과 람다, 컨셉Modern C++의 타입 추론과 람다, 컨셉
Modern C++의 타입 추론과 람다, 컨셉HyunJoon Park
 
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델Seungmo Koo
 
언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.Wuwon Yu
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012devCAT Studio, NEXON
 
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019devCAT Studio, NEXON
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심흥배 최
 
コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!amusementcreators
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012Esun Kim
 
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018devCAT Studio, NEXON
 
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉iFunFactory Inc.
 
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기Jaeseung Ha
 

Tendances (20)

GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception
GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with ExceptionGCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception
GCGC- CGCII 서버 엔진에 적용된 기술 (5) - Executor with Exception
 
Multithread & shared_ptr
Multithread & shared_ptrMultithread & shared_ptr
Multithread & shared_ptr
 
GCGC- CGCII 서버 엔진에 적용된 기술 (1)
GCGC- CGCII 서버 엔진에 적용된 기술 (1)GCGC- CGCII 서버 엔진에 적용된 기술 (1)
GCGC- CGCII 서버 엔진에 적용된 기술 (1)
 
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?
[IGC2018] 에이스프로젝트 안현석 - 유니티로 실시간 멀티플레이 게임서버를 만들 수 있을까?
 
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
조정훈, 게임 프로그래머를 위한 클래스 설계, NDC2012
 
UE4 Garbage Collection
UE4 Garbage CollectionUE4 Garbage Collection
UE4 Garbage Collection
 
Windows system - memory개념잡기
Windows system - memory개념잡기Windows system - memory개념잡기
Windows system - memory개념잡기
 
Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)Deep Dive async/await in Unity with UniTask(EN)
Deep Dive async/await in Unity with UniTask(EN)
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성
 
Modern C++의 타입 추론과 람다, 컨셉
Modern C++의 타입 추론과 람다, 컨셉Modern C++의 타입 추론과 람다, 컨셉
Modern C++의 타입 추론과 람다, 컨셉
 
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
게임서버프로그래밍 #0 - TCP 및 이벤트 통지모델
 
언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.
 
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
양승명, 다음 세대 크로스플랫폼 MMORPG 아키텍처, NDC2012
 
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
홍성우, 게임 서버의 목차 - 시작부터 출시까지, NDC2019
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
 
コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
 
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
 
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉
NDC14 범용 게임 서버 프레임워크 디자인 및 테크닉
 
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
 

Similaire à 242 naver-2

(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요? 내훈 정
 
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)내훈 정
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요NAVER D2
 
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)khuhacker
 
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅Youngmin Koo
 
[박민근] 3 d렌더링 옵티마이징_nv_perfhud
[박민근] 3 d렌더링 옵티마이징_nv_perfhud[박민근] 3 d렌더링 옵티마이징_nv_perfhud
[박민근] 3 d렌더링 옵티마이징_nv_perfhudMinGeun Park
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템QooJuice
 
IoT 개발자를 위한 Embedded C에서 TDD를 해보자
IoT 개발자를 위한 Embedded C에서 TDD를 해보자IoT 개발자를 위한 Embedded C에서 TDD를 해보자
IoT 개발자를 위한 Embedded C에서 TDD를 해보자Taeyeop Kim
 
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-1900Samsung Electronics
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010Ryan Park
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10Ryan Park
 
요람(CreateProcess)에서 무덤(ResumeThread)까지
요람(CreateProcess)에서 무덤(ResumeThread)까지요람(CreateProcess)에서 무덤(ResumeThread)까지
요람(CreateProcess)에서 무덤(ResumeThread)까지Hyoje Jo
 
2020년 11월 14일 개발자 이야기
2020년 11월 14일 개발자 이야기2020년 11월 14일 개발자 이야기
2020년 11월 14일 개발자 이야기Jay Park
 
Jnetpcap quickguide
Jnetpcap quickguideJnetpcap quickguide
Jnetpcap quickguideSukjin Yun
 
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안Jeongsang Baek
 
Pgday bdr gt1000
Pgday bdr gt1000Pgday bdr gt1000
Pgday bdr gt1000정대 천
 
Pgday bdr 천정대
Pgday bdr 천정대Pgday bdr 천정대
Pgday bdr 천정대PgDay.Seoul
 

Similaire à 242 naver-2 (20)

(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
Ndc12 2
Ndc12 2Ndc12 2
Ndc12 2
 
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
 
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
 
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅
 
[박민근] 3 d렌더링 옵티마이징_nv_perfhud
[박민근] 3 d렌더링 옵티마이징_nv_perfhud[박민근] 3 d렌더링 옵티마이징_nv_perfhud
[박민근] 3 d렌더링 옵티마이징_nv_perfhud
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템
 
IoT 개발자를 위한 Embedded C에서 TDD를 해보자
IoT 개발자를 위한 Embedded C에서 TDD를 해보자IoT 개발자를 위한 Embedded C에서 TDD를 해보자
IoT 개발자를 위한 Embedded C에서 TDD를 해보자
 
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing SystemGCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System
 
Init to systemd
Init to systemdInit to systemd
Init to systemd
 
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
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
 
요람(CreateProcess)에서 무덤(ResumeThread)까지
요람(CreateProcess)에서 무덤(ResumeThread)까지요람(CreateProcess)에서 무덤(ResumeThread)까지
요람(CreateProcess)에서 무덤(ResumeThread)까지
 
2020년 11월 14일 개발자 이야기
2020년 11월 14일 개발자 이야기2020년 11월 14일 개발자 이야기
2020년 11월 14일 개발자 이야기
 
Jnetpcap quickguide
Jnetpcap quickguideJnetpcap quickguide
Jnetpcap quickguide
 
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
소셜게임 서버 개발 관점에서 본 Node.js의 장단점과 대안
 
Pgday bdr gt1000
Pgday bdr gt1000Pgday bdr gt1000
Pgday bdr gt1000
 
Pgday bdr 천정대
Pgday bdr 천정대Pgday bdr 천정대
Pgday bdr 천정대
 

Plus de NAVER D2

[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다NAVER D2
 
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...NAVER D2
 
[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기NAVER D2
 
[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발NAVER D2
 
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈NAVER D2
 
[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&ANAVER D2
 
[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기NAVER D2
 
[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep LearningNAVER D2
 
[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applicationsNAVER D2
 
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingOld version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingNAVER D2
 
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지NAVER D2
 
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기NAVER D2
 
[224]네이버 검색과 개인화
[224]네이버 검색과 개인화[224]네이버 검색과 개인화
[224]네이버 검색과 개인화NAVER D2
 
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)NAVER D2
 
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기NAVER D2
 
[213] Fashion Visual Search
[213] Fashion Visual Search[213] Fashion Visual Search
[213] Fashion Visual SearchNAVER D2
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화NAVER D2
 
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지NAVER D2
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터NAVER D2
 
[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?NAVER D2
 

Plus de NAVER D2 (20)

[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다
 
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
 
[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기
 
[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발
 
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
 
[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A
 
[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기
 
[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning
 
[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications
 
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingOld version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
 
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
 
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
 
[224]네이버 검색과 개인화
[224]네이버 검색과 개인화[224]네이버 검색과 개인화
[224]네이버 검색과 개인화
 
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
 
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
 
[213] Fashion Visual Search
[213] Fashion Visual Search[213] Fashion Visual Search
[213] Fashion Visual Search
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화
 
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
 
[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?
 

242 naver-2

  • 1. 멀티쓰레드 프로그래밍이 왜이리 힘드나요? (컴파일러와 하드웨어에서 Lock-free 알고리즘 까지) 정내훈 한국산업기술대학교 게임공학과
  • 2. 2-2 발표자 소개 • KAIST 전산과 박사 – 전공 : 멀티프로세서 CPU용 일관성 유지 HW • NCSoft 근무 – Alterlife 프로그램 팀장 – Project M 프로그램 팀장 – CTO 직속 게임기술연구팀 • 현 : 한국산업기술대학교 게임공학과 부교수 – 학부 강의 : 게임서버프로그래밍 – 대학원 강의 : 멀티코어프로그래밍, 심화 게임서버 프로그래밍
  • 3. 2-3 참고 • NDC2012, KGC2012, CJE&M에서 강연한 내용 – 업데이트 • 삼성첨단기술연수소에서 강의한 내용 – 40시간 강의 (실습 포함) 의 앞부분 • 대학원 처음 4주 강의 분량의 압축
  • 4. 2-4 목차 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 5. 2-5 도입 • 멀티쓰레드 프로그래밍 이란? – 멀티코어 혹은 멀티프로세서 컴퓨터의 성능을 이끌어 내기 위한 프로그래밍 기법 – 흑마술의 일종 • 잘못 사용하면 패가 망신
  • 6. 2-6 도입 • 흑마술 멀티쓰레드 프로그래밍의 위험성 – “자꾸 죽는데 이유를 모르겠어요” • 자매품 : “이상한 값이 나오는데 이유를 모르겠어요” – “더 느려져요” [미] MuliThreadProgramming [mʌ́ ltiθred-|proʊgrӕmɪŋ] : 1. 흑마술, 마공 2. 위력이 강대하나 다루기 어려워 잘 쓰이지 않는 기술
  • 7. 2-7 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 8. 2-8 현실 • “멀티쓰레드 안 해도 되지 않나요?” – NO! – “MultiThread 프로그래밍을 하지 않는 이상 프로그램의 성능은 전혀 나아지지 않을 것임” – by Intel, AMD • “공짜 점심은 끝났어요~~”
  • 9. 2-9 현실 피할 곳도 숨을 곳도 없습니다.
  • 10. 2-10 현실 • 멀티쓰레드 프로그래밍을 하지 않으면? – (멀티코어) CPU가 놀아요. – 경쟁회사 제품보다 느려요. • FPS(Frames Per Second) • 동접 – 점점 줄어드는 사용자당 수입 – 만일 중국에 출동하면??
  • 11. 2-11 현실 • 멀티 코어 CPU가 왜 나왔는가? – 예전에는 만들기 힘들어서? No – 다른 방법들의 약발이 다 떨어져서! • 클럭 속도, 캐시, 슈퍼스칼라, Out-of-order, 동적 분기 예측… – 늦게 나온 이유 • 프로그래머에게 욕을 먹을 것이 뻔하기 때문. – 기존 프로그램의 성능향상이 전혀 없고, 멀티 쓰레드 프로그래밍이 너무 어려워서.
  • 12. 2-12 현실 • 컴퓨터 공학을 전공했지만 학부에서 가르치지 않았다. • 큰맘 먹고 스터디를 시작했지만 한 달도 못 가서 흐지부지 되었다. (원인은 다음 페이지) • 그냥 멀티쓰레드 안 쓰기로 했다.
  • 14. 2-14 현실 • 왜 멀티쓰레드 프로그래밍이 어려운가? – 다른 쓰레드의 영향을 고려해서 프로그램 해야 하기 때문에 – 에러 재현과 디버깅이 힘들어서 – Visual Studio가 사기를 치고 있기 때문 • 왜 멀티쓰레드 프로그래밍이 진짜로 어려운가? – CPU가 사기를 치고 있기 때문
  • 15. 2-15 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 16. 2-16 고생길 • Visual Studio의 사기 – 참조 <simple_sync> DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { data = 1; flag = true; } DWORD WINAPI ThreadFunc2(LPVOID lpVoid) { while(!flag); my_data = data; }
  • 17. 2-17 고생길 • Visual Studio의 사기 – 참조 <simple_sync> DWORD WINAPI ThreadFunc2(LPVOID lpVoid) { DWORD WINAPI ThreadFunc2(LPVOID lpVOid) while(!flag); { my_data = data; 00951020 mov al,byte ptr [flag } 00951025 00951027 싱글 쓰레드 프로그램이면? VS는 무죄! (953374h)] while (!flag); test al,al je ThreadFunc2+5 (951025h) int my_data = data; printf("Data is %Xn", my_data); mov eax,dword ptr [data (953370h)] push eax push offset string "Data is %Xn" 00951029 0095102E 0095102F (952104h) 00951034 call (9520ACh)] 0095103A add return 0; 0095103D xor } 0095103F ret dword ptr [__imp__printf esp,8 eax,eax 4
  • 18. 2-18 고생길 • Visual Studio의 사기를 피하는 방법 – volatile을 사용하면 된다. • 최적화를 하지 않는다. • 반드시 메모리를 읽고 쓴다. • 읽고 쓰는 순서를 지킨다. – 참 쉽죠? – “어셈블리를 모르면 Visual Studio의 사기를 알 수 없다” 흠좀무…
  • 19. 2-19 고생길 • 정말 쉬운가??? struct Qnode { volatile int data; volatile Qnode* next; }; DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { while ( qnode->next == NULL ) { } my_data = qnode->next->data; } 무엇이 문제일까??
  • 20. 고생길 • volatile의 사용법 – volatile int * a; • *a = 1; // 순서를 지킴 • a = b; // 순서를 지키지 않는다. – int * volatile a; • *a = 1; // 순서를 지키지 않음, • a = b; // 이것은 순서를 지킴
  • 21. 고생길 • 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 011F108C 011F1090 011F1092 01191090 01191093 01191095 mov test je eax,dword ptr [esi+4] eax,eax ThreadFunc+90h (1191090h) mov lea cmp je eax,dword ptr [esi+4] esp,[esp] eax,ebx ThreadFunc+90h (11F1090h)
  • 22. 2-22 고생길 • 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; }
  • 23. 2-23 고생길 • Thread 2개로 합계 1억을 만드는 프로그램의 최신 유행
  • 26. 2-26 고생길 • 왜 틀린 결과가 나왔을까? ─ “sum+=2”가 문제이다.
  • 27. 2-27 고생길 • 왜 틀린 결과가 나왔을까? – DATA RACE (복수의 쓰레드에서 같은 공유 메모리에 WRITE하는 행위) 때문. – “sum+=2”가 문제이다. 쓰레드 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
  • 28. 2-28 고생길 • 하지만 ADD SUM, 2 • 이 출동하면? • 왜???? – 대학 2학년 때 컴퓨터구조와 운영체제시간에 배움
  • 29. 2-29 고생길 • Data Race의 해결 방법은? – Data Race를 없애면 된다. • 어떻게 – Lock과 Unlock을 사용한다. – Windows에서는 EnterCriticalSection(), LeaveCriticalSection() – Linux에서는 pthread_mutex_lock(), pthread_mutex_un – C++11에서는 std::mutex의 lock(), unlock()
  • 31. 2-31 고생길 • 결과가 옳게 나왔다. 만족하는가? 실행시간 결과 1 Thread 280,577 100000000 2 Thread 146,823 50876664 4 Thread 132,362 27366758 No LOCK 실행시간 1 Thread 2,888,071 100000000 2 Thread 35배의 성능차이 결과 5,947,291 100000000 4 Thread 4,606,754 100000000 With LOCK
  • 32. 2-32 고생길 • EnterCriticalSection() 이라는 물건은 – 한번에 하나의 쓰레드만 실행 시킴 – Lock을 얻지 못하면 시스템 호출
  • 33. 2-33 고생길 • 해결 방법은? – Lock을 쓰지 않으면 된다. – “Sum += 2”를 Atomic하게 만들면 된다. • Atomic – 실행 중 다른 Core가 끼어들지 못하도록 한다.
  • 34. 2-34 고생길 • 결과가 옳게 나왔다. 만족하는가? 실행시간 결과 1 Thread 280,577 100000000 2 Thread 146,823 50876664 4 Thread 132,362 27366758 실행시간 결과 1 Thread 1,001,528 100000000 2 Thread 1,462,121 100000000 4 Thread 1,452,311 결과 1 Thread No LOCK 실행시간 2,888,071 100000000 2 Thread 5,947,291 100000000 4 Thread 4,606,754 100000000 100000000 With LOCK With InterlockedOperation
  • 36. 2-36 고생길 • 만족하는가? (i7 – 4core) 실행시간 결과 1 Thread 280,577 100000000 2 Thread 146,823 4 Thread 132,362 실행시간 결과 1 Thread 2,888,071 100000000 50876664 2 Thread 5,947,291 100000000 27366758 4 Thread 4,606,754 100000000 No LOCK With LOCK 실행시간 결과 실행시간 결과 1 Thread 1,001,528 100000000 1 Thread 287,776 100000000 2 Thread 1,462,121 100000000 2 Thread 156,394 100000000 4 Thread 1,452,311 100000000 4 Thread 96,925 100000000 With InterlockedOperation 정답
  • 37. 2-37 고생길 • 만족하는가? (XEON E5405, 2CPU) 실행시간 결과 1 Thread 1,798 100000000 2 Thread 2,926 4 Thread 8 Thread 실행시간 결과 1 Thread 27,073 100000000 520251348 2 Thread 83,586 100000000 1,692 203771150 4 Thread 69,264 100000000 3,699 193307522 8 Thread 67,156 100000000 No LOCK With LOCK 실행시간 결과 1 Thread 11,307 100000000 2 Thread 24,194 4 Thread 8 Thread 실행시간 결과 1 Thread 2055 100000000 100000000 2 Thread 1798 100000000 20,292 100000000 4 Thread 834 100000000 18,699 100000000 8 Thread 419 100000000 With InterlockedOperation 정답
  • 38. 2-38 고생길 • 만족하는가? (XEON E5-4620, 4CPU, 32Core) 실행 시간 결과 1 Thread 0.425 100000000 2 Thread 0.678 4 Thread 실행 시간 결과 1 Thread 1.703 100000000 56567966 2 Thread 13.21 100000000 0.768 27254540 4 Thread 11.45 100000000 8 Thread 1.009 16257652 8 Thread 32.27 100000000 16Thread 0.942 14320406 16Thread 46.10 100000000 32Thread 1.706 8570996 32Thread 80.76 100000000 64Thread 1.926 3855910 64Thread 80.32 100000000 No LOCK 결과 1 Thread 0.877 100000000 2 Thread 3.344 4 Thread 실행 시간 결과 1 Thread 0.422 100000000 100000000 2 Thread 0.328 100000000 2.653 100000000 4 Thread 0.168 100000000 8 Thread Interlocked Operation 실행 시간 With LOCK 2.515 100000000 8 Thread 0.080 100000000 16Thread 2.624 100000000 16Thread 0.080 100000000 32Thread 3.353 100000000 32Thread 0.043 100000000 64Thread 3.061 100000000 64Thread 0.028 100000000 정답
  • 39. 2-39 고생길 • 지금까지 – Visual Studio의 마수에서 벗어나기 • Volatile을 잘 쓰자 – 경쟁상태 해결하기. • Lock을 최소화 하자 • Lock대신 atomic operation을 사용하자
  • 40. 2-40 고생길 • 그러나 – 절대로 모든 문제가 정답처럼 풀리지 않는다. – Interlocked로 구현 가능하면 다행 (atomic) • Interlock이 가능한 것은 일부 Instruction – 일반적인 자료구조를 Lock없이 Atomic하게 구현하는 것은 큰 문제다. – Lock말고도 다른 문제가 있다.
  • 41. 2-41 HELL • 멀티 코어에서는 Data Race말고도 다른 문제점이 있다. • “상상한 것 그 이상을 보여준다”, 충공깽
  • 43. 2-43 HELL • 다음의 프로그램으로 Lock과 Unlock이 동작할까? – 피터슨 알고리즘 – 두 개의 쓰레드에서 Lock구현 – 운영체제 교과서에 실려 있음 – threadId는 0과 1이라고 가정 (myID에 저장) • 실행해 보자 • 결과는? volatile int victim = 0; volatile bool flag[2] = {false, false}; Lock(int myID) { int other = 1 – myID; flag[myID] = true; victim = myID; while(flag[other] && victim == myID) {} } Unlock (int myID) { flag[myID] = false; }
  • 45. 2-45 HELL • 이유는? – CPU는 사기를 친다. • Line Based Cache Sharing • Out of order execution • write buffering – CPU는 프로그램을 순차적으로 실행하는 척만한다. • 자기 자신이 실행하는 프로그램에게는 제대로 실행하는 것처럼 거짓말한다. • 옆의 Core에서 보면 거짓말이 보인다.
  • 46. 2-46 HELL • Out-of-order 실행 a = fsin(b); f = 3; a = b; c = d; // a,b는 cache miss // c,d는 cache hit
  • 47. 2-47 HELL • 문제는 메모리 – 프로그램 순서대로 읽고 쓰지 않는다. • 읽기와 쓰기는 시간이 많이 걸리므로. • 옆의 프로세서(core)에서 보면 속도차와 실행순서 뒤바뀜이 보인다. • 어떠한 일이 벌어지는가?
  • 48. 2-48 병행성과 정확성 • 아래의 두 개의 실행결과는 서로 다르다 어떠한 것이 정확한 결과인가? thread a thread b write (x, 1) write(x, 2) read(x, 2) Type-A read(x, 2) Type-B !! thread a thread b write (x, 1) read(y, 1) write (y, 1) read(x, 0)
  • 49. 2-49 병행성과 정확성 • 그러면 이것은? thread a thread b write (x, 1) write(x, 2) read(x, 2) Type-C!! read(x, 1) thread a thread b write (x, 1) write(y, 1) read (y, 0) Read(x, 0) Type-D!!
  • 50. 2-50 HELL • 현실 – 앞의 여러 형태의 결과는 전부 가능하다. • 부정확해 보이는 결과가 나오는 이유? – 현재의 CPU는 Out-of-order실행을 한다. – 메모리의 접근은 순간적이 아니다. – 멀티 코어에서는 옆의 코어의 Out-of-order 실행이 관측된다.
  • 51. 2-51 HELL • 진짜? • 확인해 보자. • 메모리 접근 순서를 강제로 맞추어 주는 명령어 _asm mfence; • 앞에 피터슨 알고리즘에 적용해보자. – 근데… 오류의 확률이 낮아서…
  • 52. 2-52 HELL • 메모리 접근 오류 검출 프로그램을 사용해보자 • 아이디어. – 메모리 내용을 계속 업데이트 하면서 다른 쓰레드의 업데이트를 같이 기록하여 나중에 기록된 로그를 비교해 보자.
  • 53. 2-53 HELL • 정말 간단한 프로그램 #define THREAD_MAX 2 #define SIZE 10000000 volatile int x,y; int trace_x[SIZE], trace_y[SIZE]; DWORD WINAPI ThreadFunc0(LPVOID a) { for(int i = 0; i <SIZE;i++) { x = i; trace_y[i] = y; } return 0; } int main() { DWORD addr; HANDLE hThread[THREAD_MAX]; DWORD WINAPI ThreadFunc1(LPVOID a) { for(int i = 0; i <SIZE;i++) { y = i; trace_x[i] = x; } return 0; } .. // Thread 2개 실행 int count = 0; for (int i=0; i< SIZE;++i) 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; count++; } printf("Total Memory Inconsistency:%dn", count); return 0; }
  • 54. 2-54 HELL • 프로그램 설명 8보다 3이 먼저 write!! 2 3 4 5 6 7 7 7 8 8 8 9 x traceY 6 7 8 9 10 11 y 1 2 2 3 5 6 traceX 3보다 8이 먼저 write!!!
  • 56. 2-56 HELL • 메모리 변경 순서가 뒤바뀔 확률은? • _asm mfence를 넣어보자. – 또는 C++11에서 #include <atomic> std::atomic_thread_fence(std::memory_order_seq_cst);
  • 57. 2-57 병행성과 정확성 • 메모리에는 유령이 volatile bool done = false; volatile int *bound; int error; DWORD WINAPI ThreadFunc1(LPVOID lpVoid) { for (int j = 0; j<= 25000000; ++j) done = true; return 0; } *bound = -(1 + *bound); DWORD WINAPI ThreadFunc2(LPVOID lpVOid) { while (!done) { int v = *bound; if ((v !=0) && (v != -1)) error ++; } return 0; }
  • 58. 2-58 병행성과 정확성 • 어떻게 실행했길래? int ARR[32]; int temp = (int) &ARR[16]; temp = temp & 0xFFFFFFC0; temp -= 2; bound = (int *) temp; *bound = 0; HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunc1, (LPVOID) 0, 0, &addr); HANDLE hThread3 = CreateThread(NULL, 0, ThreadFunc2, (LPVOID) 1, 0, &addr);
  • 60. 2-60 HELL • 결과가…. – 중간값 • write시 최종값과 초기값이 아닌 다른 값이 도중에 메모리에 써지는 현상 – 이유는? • Cache Line Size Boundary – 대책은? • Pointer를 절대 믿지 마라. • Byte 밖에 믿을 수 없다. • Pointer가 아닌 변수는 – Visual C++ 또는 G++가 잘 해준다. short buf[256] buf[0] = length; buf[1] = OP_MOVE; *((float *)(&buf[2])) = x; *((float *)(&buf[4])) = y; *((float *)(&buf[6])) = z; *((float *)(&buf[8])) = dx; *((float *)(&buf[10])) = dy; *((float *)(&buf[12])) = dz; *((float *)(&buf[14])) = ax; *((float *)(&buf[16])) = ay; *((float *)(&buf[18])) = az; *((int *)(&buf[20])) = h; … send( fd, buf, (size_t)buf[0], 0 ); 어디서 많이 본 소스코드..
  • 61. 2-61 HELL • 이러한 현상을 메모리 일관성(Memory Consistency) 문제라고 부른다. – x86은 얌전한 편, ARM CPU는 더하다. http://en.wikipedia.org/wiki/Memory_ordering
  • 62. 2-62 HELL • 정리 – 멀티쓰레드에서의 공유 메모리 • 다른 코어에서 보았을 때 업데이트 순서가 틀릴 수 있다. • 메모리의 내용이 한 순간에 업데이트 되지 않을 때 도 있다. – 일반적인 프로그래밍 방식으로는 멀티쓰레드에서 안정적으로 돌아가는 프로그램을 만들 수 없다.
  • 63. 2-63 HELL • 어떻게 할 것인가? – 위의 상황을 감안하고 프로그램 작성 • 프로그래밍이 너무 어렵다. – 피터슨이나 빵집 알고리즘도 동작하지 않는다. – 모든 공유메모리 접근을 Atomic하도록 수정한다. • 모든 메모리 접근을 Lock/Unlock으로 막으면 가능 – 성능저하!!!, Lock은 어떻게 구현? • Interlocked Operation 사용 – 간단한 연산만 가능, 성능저하 • mfence의 적절한 추가 – 적절하다는 보장은??? 어쩌라고???
  • 64. 2-64 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 65. 2-65 희망 • 언젠가는 메모리에 대한 쓰기가 실행 된다. • 자기 자신의 프로그램 실행순서는 지켜진다. • 캐시의 일관성은 지켜진다. – 한번 지워졌던 값이 다시 살아나지는 않는다. – 언젠가는 모든 코어가 동일한 값을 본다 • 캐시라인 내부의 쓰기는 중간 값을 만들지 않는다.
  • 66. 2-66 희망 • 우리가 할 수 있는 것 – CPU의 여러 삽질에도 불구 하고 주의 깊게 프로그래밍 하면 모든 메모리 접근을 Atomic하게 할 수 있다. • HW의 도움 없이도 가능. • 하지만 mfence가 효율적
  • 67. 2-67 희망 • Atomic Memory 만 있으면 되는가? – NO • 진짜 큰 규모의 상용 멀티쓰레드 프로그래밍은? – 쓰레드간의 동기화나 자료 전송은 고유의 자료구조 사용 • Queue, Stack, List, Map, Tree…… • 예) Tera의 시야처리용 Lock-free job queue • 예) Unreal3의 rendering command queue
  • 68. 2-68 희망 • 하지만. – 지금까지 배운 모든 자료구조가 멀티쓰레드에서는 동작하지 않는다. – STL도 동작하지 않는다. – 다시 작성해야 한다. • LOCK을 쓰면? “Lock 없애야 해요 Lock 없앨 때 마다 동접이 300명씩 늘어났어요.” - N모사에서 L모 게임을 만들었던 S모님
  • 69. 2-69 Lock없는 프로그램 • 효율적인 구현 – Lock없는 구현 • 성능 저하의 주범이므로 당연 – Overhead & Critical Section – Priority inversion – Convoying – Lock이 없다고 성능저하가 없는가?? • 상대방 쓰레드에서 어떤 일을 해주기를 기다리는 한 동시실행으로 인한 성능 개선을 얻기 힘들다. – while (other_thread.flag == true); – lock과 동일한 성능저하 • 상대방 쓰레드의 행동에 의존적이지 않는 구현방식이 필요하다.
  • 70. 2-70 Non-Blocking • 블럭킹 (blocking) – 다른 쓰레드의 진행상태에 따라 진행이 막힐 수 있음 • 예) while(lock != 0); – 멀티쓰레드의 bottle neck이 생긴다. – Lock을 사용하면 블럭킹 • 넌블럭킹 (non-blocking) – 다른 쓰레드가 어떠한 삽질을 하고 있던 상관없이 진행 • 예) 공유메모리 읽기/쓰기, Interlocked Operation
  • 71. 2-71 Non-Blocking • 블럭킹 알고리즘의 문제 – 성능저하 – Priority Inversion • Lock을 공유하는 덜 중요한 작업들이 중요한 작업의 실행을 막는 현상 • Reader/Write Problem에서 많이 발생 – Convoying • Lock을 얻은 쓰레드가 스케쥴링에서 제외된 경우, lock을 기다리는 모든 쓰레드가 공회전 • Core보다 많은 수의 thread를 생성했을 경우 자주 발생. • 성능이 낮아도 Non-Blocking이 필요할 수 있다.
  • 72. 2-72 Non-Blocking • 넌블럭킹의 등급 – 무대기 (wait-free) • 모든 메소드가 정해진 유한한 단계에 실행을 끝마침 • 멈춤 없는 프로그램 실행 – 무잠금 (lock-free) • • • • 항상, 적어도 한 개의 메소드가 유한한 단계에 실행을 끝마침 무대기이면 무잠금이다 기아(starvation)을 유발하기도 한다. 성능을 위해 무대기 대신 무잠금을 선택하기도 한다.
  • 73. 2-73 Non-Blocking • 정리 – Wait-free, Lock-free • Lock을 사용하지 않고 • 다른 쓰레드가 어떠한 행동을 하기를 기다리는 것 없이 • 자료구조의 접근을 Atomic하게 해주는 알고리즘의 등급 – 멀티 쓰레드 프로그램에서 쓰레드 사이의 효율적인 자료 교환과 협업을 위해서는 NonBlocking 자료 구조가 필요하다.
  • 74. 2-74 병행성과 정확성 • 그러면, Atomic Memory로 그런 자료구조를 만들면 되지 않는가? • Atomic Memory만으로는 다중 쓰레드 무대기 큐를 만들 수 없다!!!!!! – (증명) : 아까 그 책
  • 75. 2-75 병행성과 정확성 • 다중 쓰레드 무대기 큐를 만들려면? – CAS 명령어가 필요하다. − CAS가 없이는 대부분의 non-blocking 알고리즘들을 구현할 수 없다. • Queue, Stack, List… − CAS를 사용하면 모든 싱글쓰레드 알고리즘 들을 Lock-free 알고리즘으로 변환할 수 있다!!! − Lock-free 알고리즘의 핵심
  • 76. 2-76 CAS • CAS − CAS(&A, old, new); − 의미 : 아래의 연산을 Atomic하게 수행 if (A == old) { A = new; return true; } else return false; − 다른 버전의 의미 : A메모리를 다른 쓰레드가 먼저 업데이트 해서 false가 나왔다. 모든 것을 포기하라.
  • 77. 2-77 CAS • 구현 : Windows – API #include <windows.h> LONG __cdecl InterlockedCompareExchange( __inout LONG volatile *Destination, __in LONG Exchange, __in LONG Comparand ); – CAS의 구현 Bool CAS(LONG volatile *Addr, LONG New, LONG Old) { LONG temp = InterlockedCompareExchange(Addr, New, Old); return temp == Old; }
  • 78. 2-78 CAS • 구현 : LINUX #include <stdbool.h> bool CAS(int *ptr, int oldval, int newval) { return __sync_bool_compare_and_swap(ptr, oldval, newval); }
  • 79. 2-79 CAS • 구현 : C++11 #include <atomic> bool atomic_compare_exchange_strong( std::atomic<T>* obj, T* expected, T desired );
  • 80. 2-80 CAS • 실제 HW (x86 계열 CPU) 구현 – LOCK prefix와 CMPXCHG 명령어로 구현 – lock cmpxchg [A], b 기계어 명령으로 구현 • eax에 비교값, A에 주소, b에 넣을 값 if (eax == [a]) { ZF = true; [a] = b; } else { ZF = false; eax = [a]; }
  • 81. 2-81 CAS • 실제 HW (ARM) 구현 static inline AtomicWord CompareAndSwap(volatile AtomicWord* ptr, AtomicWord old_value, AtomicWord new_value) { uint32_t old, tmp; __asm__ __volatile__("1: @ atomic cmpxchgn" "mov %0, #0n" "ldrex %1, [%2]n" "teq %1, %3n" "strexeq %0, %4, [%2]n" "teq %0, #0n" "bne 1b" : "=&r" (tmp), "=&r" (old) : "r" (ptr), "Ir" (old_value), "r" (new_value) : "cc"); return old; }
  • 82. 2-82 CAS • CAS의 위용 – 모든 자료구조를 멀티쓰레드 무대기자료구조로 만들 수 있다. • 증명이 되어 있다. – 바꿔주는 프로그램이 있다. – STL도 OK!
  • 83. 2-83 CAS • 모든 자료구조를 멀티쓰레드 Lock-Free로 바꿔주는 프로그램 class LFUniversal { private: Node *head[N], Node tail; public: LFUniversal() { tail.seq = 1; for (int i=0;i<N;++i) head[i] = &tail; } Response apply(Invocation invoc) { int i = Thread_id(); Node prefer = Node(invoc); while (prefer.seq == 0) { Node *before = tail.max(head); Node *after = before->decideNext->decide(&prefer); before->next = after; after->seq = before->seq + 1; head[i] = after; } SeqObject myObject; Node *current = tail.next; while (current != &prefer) { myObject.apply(current->invoc); current = current->next; } return myObject.apply(current->invoc); } };
  • 84. 2-84 희망 • Happy End???? – NO • 왜? – 구현은 쉽다. – 성능이 엉망이다.
  • 85. 이론 시간 • XEON, E5-4620, 2.2GHz, 4CPU (32 core) • STL의 queue를 무잠금, 무대기로 구현한 것과, CriticalSection으로 atomic하게 만든 것의 성능 비교. – Test조건 : 16384번 Enqueue, Dequeue (결과는 mili second) – EnterCriticaSection()을 사용한 것은 테스트 데이터의 크기가 100배 – 따라서 100배 성능 차이 (4개 thread의 경우) 쓰레드 갯수 1 2 4 8 16 32 64 무잠금 만능 3749 1966 1697 1120 742 525 413 무대기 만능 3640 1964 1219 1136 577 599 448 EnterCritical 232 822 1160 1765 1914 4803 7665 • 그렇다면, EnterCriticalSection을 사용해야 하는가? – No : 멀티쓰레드에서의 성능향상이 없다.
  • 86. 2-86 희망 • 결론 – CPU가 제공하는 CAS 명령어를 사용하면 기존의 모든 싱글쓰레드 알고리즘을 Lockfree한 멀티쓰레드 알고리즘으로 변환할 수 있다. • 현실 – Universal Algorithm은 비효율 적이다.
  • 87. 2-87 희망 • 대안 – 자료구조에 맞추어 최적화된 lockfree알고리즘을 일일이 개발해야 한다. • 멀티쓰레드 프로그램은 힘들다. => 연봉이 높다. • 다른 데서 구해 쓸 수도 있다. – Intel TBB, VS2012 PPL – 인터넷 – 하지만 범용적일 수록 성능이 떨어진다. 자신에게 딱 맞는 것을 만드는 것이 좋다.
  • 88. 2-88 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길
  • 89. 2-89 Non-Blocking • 우리의 목적 – 정확한 결과 – 고성능 • 번역하면 – Lock을 사용하지 않고 – 비멈춤 (wait-free, lock-free) – 자료구조 (Queue, Stack, List~~~)
  • 90. 2-90 Non-Blocking • 지향하는 프로그래밍 스타일 – Lock을 사용한 프로그래밍 • Blocking • 느림 (몇 백배) – 원자적 레지스터를 사용한 프로그래밍 • 표현력이 떨어짐 • Queue도 만들지 못함 – Non-blocking 자료구조를 사용한 프로그래밍 • OK
  • 91. 2-91 내용 • 도입 • 현실 • Welcome to the Hell. • 새로운 희망 • 우리의 나아갈 길 => 실제 예제
  • 92. 예제 • Non Blocking 자료 구조의 구현 법 • 예제 : 정렬된 링크드 리스트를 사용한 집합 – int를 집합에 add(), remove(), find()할 수 있는 자료 구조 • 성능 비교
  • 93. 리스트의 구현 • 1차 구현 : Lock의 사용 bool Remove(int key) { NODE *pred, *curr; pred = &head; EnterCriticalSection(&glock); curr = pred->next; while (curr->key < key) { pred = curr; curr = curr->next; } if (key == curr->key) { pred->next = curr->next; delete curr; LeaveCriticalSection(&glock); return true; } else { LeaveCriticalSection(&glock); return false; } }
  • 94. 리스트의 구현 • 2 차 구현 : Lock의 세밀화 bool Remove(int key) { NODE *pred, *curr; head.lock(); pred = &head; curr = pred->next; curr->lock(); while (curr->key < key) { pred->unlock(); pred = curr; curr = curr->next; curr->lock(); } if (key == curr->key) { pred->next = curr->next; curr->unlock(); pred->unlock(); delete curr; return true; } else { curr->unlock(); pred->unlock(); return false; } }
  • 95. 리스트의 구현 • 3차 구현 : Lock감소 bool validate(NODE *pred, NODE *curr) { NODE *node = &head; while (node->key <= pred->key) { if (node == pred) return pred->next == curr; node = node->next; } return false; } bool Remove(int key) { NODE *pred, *curr; while(true) { pred = &head; curr = pred->next; while (curr->key < key) { pred = curr; curr = curr->next; } pred->lock(); curr->lock(); if (!validate(pred, curr)) { curr->unlock(); pred->unlock(); continue; } if (key == curr->key) { pred->next = curr->next; curr->unlock(); pred->unlock(); // delete curr; return true; } else { curr->unlock(); pred->unlock(); return false; } } }
  • 96. 리스트의 구현 • 4차구현 : 마킹 활용 bool validate(NODE *pred, NODE *curr) { return (!pred->marked) && (!curr->marked) && (pred->next == curr); } bool Remove(int key) { NODE *pred, *curr; while(true) { pred = &head; curr = pred->next; while (curr->key < key) { pred = curr; curr = curr->next; } pred->lock(); curr->lock(); if (!validate(pred, curr)) { curr->unlock(); pred->unlock(); continue; } if (key == curr->key) { curr->marked = true; pred->next = curr->next; curr->unlock(); pred->unlock(); // delete curr; return true; } else { curr->unlock(); pred->unlock(); return false; } } }
  • 97. 리스트의 구현 • 5차 구현 : Lock free class LFNODE { ... bool CompareAndSet(int old_v, int new_v) { int orig_v = InterlockedCompareExchange(reinterpret_cast<unsigned int *>(&next), new_v, old_v); return orig_v == old_v; } bool CAS(LFNODE *old_node, LFNODE *new_node, bool oldMark, bool newMark) { int oldvalue = reinterpret_cast<int>(old_node); if (oldMark) oldvalue = oldvalue | 0x01; else oldvalue = oldvalue & 0xFFFFFFFE; int newvalue = reinterpret_cast<int>(new_node); if (newMark) newvalue = newvalue | 0x01; else newvalue = newvalue & 0xFFFFFFFE; return CompareAndSet(oldvalue, newvalue); } bool AttemptMark(LFNODE *old_node, bool newMark) { int oldvalue = reinterpret_cast<int>(old_node); int newvalue = oldvalue; if (newMark) newvalue = newvalue | 0x01; else newvalue = newvalue & 0xFFFFFFFE; return CompareAndSet(oldvalue, newvalue); } LFNODE *GetNextWithMark(bool *mark) { int temp = reinterpret_cast<int>(next); *mark = (0 != (temp & 0x01)); return reinterpret_cast<LFNODE *>(temp & 0xFFFFFFFE); } bool Remove(int key) { LFNODE *GetReference() LFNODE *pred, *curr; { int temp = reinterpret_cast<int>(next); return reinterpret_cast<LFNODE *>(temp & 0xFFFFFFFE); void Find(LFNODE **Pred, LFNODE **Curr, int key) { LFNODE *pred = NULL; LFNODE *curr = NULL; LFNODE *succ = NULL; bool marked = false; ng_retry: while(true) { pred = &head; curr = pred->GetReference(); while (true) { succ = curr->GetNextWithMark(&marked); while (marked) { if (false == pred->CAS(curr, succ, false, false)) goto ng_retry; curr = succ; succ = curr->GetNextWithMark(&marked); } if (curr->key >= key) { *Pred = pred; Curr = curr; return; } pred = curr; curr = succ; } } } while(true) { Find(&pred, &curr, key); LFNODE *AtomicMarkableReference(LFNODE *ptr, bool mark) if (key != curr->key) return false; { int temp = reinterpret_cast<int>(ptr); if (mark) temp = temp | 0x01; LFNODE *succ = curr->GetReference(); else temp = temp & 0xFFFFFFFE; return reinterpret_cast<LFNODE *>(temp); == curr->AttemptMark(succ, true)) continue; if (false } pred->CAS(curr, succ, false, false); return true; } } } };
  • 98. 2-98 속도 비교 • 1과 1000사이의 숫자의 랜덤한 4백만회 삽입/삭제/검색 (i7-920) 쓰레드 개수 1차 2차 3차 4차 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
  • 99. 2-99 정리 • 공유메모리를 사용한 동기화는 사용하기 힘들다. – 일관성, 중간 값 – Atomic memory의 한계 • 공유 자료 구조를 사용해야 한다. • 좋은 공유 자료 구조는 만들기 힘들다. – Non-blocking 알고리즘의 작성은 까다롭다. – 상용 라이브러리도 좋다. Intel TBB, VS2010 PPL(Parallel Patterns Library)등 – ??NOBEL library, Concurrency Kit
  • 100. 2-100 미래 • 그래도 멀티쓰레딩은 힘들다. – 서버 프로그래머 연봉이 높은 이유 • Core가 늘어나면 지금 까지의 방법도 한계 – lock-free. wait-free overhead증가 – interlocked operation overhead증가 • 예측 – Transactional Memory – 새로운 언어의 필요 • 예) Erlang, Haskell
  • 101. 2-101 TIP • 절대로 경험을 믿지 마라!!! – 에러 날 확률이 로또 이하인 경우가 비일비재 – 디버깅 할 때, 사내 테스트 할 때는 멀쩡하다가 오픈베타 때 대형사고가 난다!! – Correct가 증명된 알고리즘이나, 믿을 수 있는 회사에서 작성한 non-Blocking 프로그램을 사용하라. • 자신이 만든 알고리즘이면 증명해봐라. (증명 방법은 교재 참조)
  • 102. 2-102 TIP • 클라우드환경은 다르다. – 많은 가상머신에서 CompareAndSwap 오퍼레이션의 딜레이가 급증하는 현상이 있다. – Parallels on MaxOS-X (OK) – VMWare, Parallels, VirtualBox on Windows-7 (성능저하)
  • 103. 2-103 NEXT • 다음 발표(내년???) – Lock-free 프로그래밍 근본적 이해 – 실제 MMO서버에서의 Lock-ree 성능 향상 – Transactional Memory with intel RTM • 그 다음 발표??? − Lock-free search : SKIP-LIST − ABA Problem, aka 효율적인 reference counting − 고성능 MMO서버를 위한 non-blocking 자료구조의 활용
  • 104. 2-104 Q&A • 연락처 – nhjung@kpu.ac.kr – 발표자료 : ftp://210.93.61.41 id:ndc21 passwd: 바람의나라 • 또는 www.slideshare.net 에서 발표제목 검색 • 참고자료 – Herlihy, Shavit, “The Art of Multiprocesor Programming, revised”, Morgan Kaufman, 2012 – 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