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
• “공짜 점심은 끝났어요~~”
9. 2-9
현실
• 멀티쓰레드 프로그래밍을 하지 않으면?
– (멀티코어) CPU가 놀아요.
– 경쟁회사 제품보다 느려요.
• FPS
• 동접
10. 2-10
현실
• 멀티 코어 CPU가 왜 나왔는가?
– 예전에는 만들기 힘들어서? No
– 다른 방법들의 약발이 다 떨어져서!
• 클럭 속도, 캐시, 슈퍼스칼라, Out-of-order, 동적 분기
예측…
– 늦게 나온 이유
• 프로그래머에게 욕을 먹을 것이 뻔하기 때문.
11. 2-11
현실
• 컴퓨터 공학을 전공했지만 학부에서
가르치지 않았다.
• 큰맘 먹고 스터디를 시작했지만 한 달도 못
가서 흐지부지 되었다. (원인은 다음 페이지)
• 그냥 멀티쓰레드 안 쓰기로 했다.
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의 사기를 알 수 없다”
흠좀무…
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;
}
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을 사용한다.
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;
}
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
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!
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;
}
}
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)
• 회사로 출장 강의
• 등록금 공짜 (정부지원)
• 석사학위 취득