Thread, OpenMP, blocks, Grand Central 그리고 OpenCL

Snow Leopard가 나오고서 참 많은 것이 변했다. 아니다. 사실 Leopard때부터 무척 많이 변했다. 대개들 Apple이 전면에 내세우는 기능들만 생각하는데, 가만히 보면 이런 저런 편리한 기능들이 참 많이 나왔다.

뭐 여기서 일일히 그런 것을 다 언급하는 것은 그렇고..
제일 궁금한 것에 대한 생각을 정리해보기로 하자.

Snow Leopard가 내가 볼때 지금까지의 Mac OS X 중 가장 많이 변경을 가한 것으로 보는 중심에는 multiprocessing/multi-threading에 대한 것 때문이다. PPC 코드를 정리한 것만도 상당한 것이지만 그건 오히려 기존 것에서 없앤거니까 그렇고.. 아마 구조 자체에 대한 정리와 재설계는 아무래도 이 multiprocessing/multi-threading이 야기했을 것이다.

전체적으로 정리해보면 Snow Leopard에서 내세운 멀티 태스킹 관련한 기술은 두가지로 나눌 수있다.

  1. Multi-processor의 활용
  2. CPU와 GPU간의 cross-multiprocessing

우선 첫번째, multi-processor를 활용한다는 점은 다음과 같다. 어떤 한 태스크를 수행하는데 있어서, thread가 되었건 process가 되었건, 여러개의 프로세싱 유닛에 일을 분산 시킨다. 요새는 하다못해 Mac mini와 같은 저렴한 컴퓨터도 멀티 코어 구조를 하고 있다. 즉 이것은 두개의 프로세서를 한개의 패키지에 넣은 것이다. Mac Pro를 보면 8개의 코어를 쓰고 있다. 즉 한개의 프로세서 유닛당 4개의 코어를 가졌는데, 그런 프로세서를 두개를 가진 것이다. 이 첫번째 multi-processor를 활용하는 것은, 특별하게 프로그래머가 프로세스나 쓰레드를 만들어서 각 코어에 할당시키고 일을 시키는 것이 아니라, 간편하게 할 수있도록 Apple이 API를 제공해 준다. NSBlockOperation이나 NSOperationQueue등을 이용해서 간편하게 할 수있도록 해준다. 물론 이렇게 준비된 새로운 class와 method를 써야 하기 때문에, 명백하게 투명한 것은 아니다. 기존의 코드에서 아무런 변화를 주지 않고도 해야 투명한 것이니까 말이다. 하지만 이 부분은 현재로써는 어쩔 수없는 접근이 아닌가 한다.

두번째는 CPU와 GPU 어디에건 task를 수행할 수있게 하는 것이다. OpenCL이 그것인데, 엄밀하게 말하자면 이건 GPU에 일반적인 연산 동작을 수행할 수있게 해주는 것이다. 사실 이말은 좀 어폐가 있다. GPU도 하나의 처리 장치로 이미 controller가 아닌 GPU라고 부른 시점부터 연산은 가능했었다. 이를테면 vertex processing 같은게 그런게 아니겠는가? 하지만 OpenCL은 그것을 일반 연산도 쉽게 GPU에 올리도록 한 것이다. 그런데 여기에 좀 재미난 점이 있다. 이전에 MMX나 SSE등도 대개 그래픽스에 관련된 연산을 빠르게 하도록 CPU 제작회사들이 CPU에 넣은 것이 아니던가? MMX나 SSE 명령을 보면 8bytes나 16bytes 단위로 데이터를 레지스터에 로드해서 병렬 처리가 되게 해 놓았다. 바로 SIMD 구조인 것인데.. 이건 딱 보면 MPEG의 DCT 테이블 계산용이란게 눈에 확 보인다. 아무튼..

근데 재미난 것은 OpenCL을 이용하려는 경우, CPU의 SIMD를 이용하는 Accelerator framework이나 아니면 Grand Central을 이용하는 경우, 즉 CPU를 이용하는 분산 처리에 비해 과연 속도가 좋을까 하는 점이다. 기분에 느릴 거 같다. 단 CPU를 이용할때는 말 그대로 CPU가 바뻐지니까, 다른 일도 할 필요가 있을때는 전반적으로 load가 올라갈 것이다. 어떨때 그럼 OpenCL을 이용할 것인가? 헷갈려지는 부분이다. Apple도 문서에 어떨때 OpenCL을 쓰는게 좋은지에 대한 설명이 없다.

기분에 Apple이 그 다음에 준비할 것은 OpenCL을 아우르는 Grand Central이 될 것 같다. 구조적으로 가능한지는 모르겠지만 개념적으로 보면, GPU와 CPU를 한 pool로 보고, 그냥 그 pool에 태스크를 던져주면 시스템이 알아서 분산해주는 모델이 합리적이지 않겠는가?

자…  주제를 좀 바꿔서..

이제 생각할 것은 thread와 OpenMP 그리고 block 대한 것이다.

thread를 이용할 때는 pthread에 기반해서 만든다. 물론 NSThread와 같은 Cocoa 자체가 준비한 것이 있지만, 이것도 결국은 pthread를 감싸서 만든 것이다. 그래서 NSThread의 method를 보면 그 티가 확 난다. 난 NSThread를 좋아하는 편인데, thread를 편하게 쓸 수있게 해주고, 또한 Unix 코드를 조금만 손봐서 완전히 Cocoa 코드로 바꿀 수가 있다는 점이 매력이다. 즉 logic flow를 바꿀 필요가 거의 없다는 점이다. Leopard에서 부터인가는 NSOperation과 NSOperationQueue가 새로 생겼다. 즉 이 둘을 이용하면 이젠 NSThread도 만들 필요가 없다. NSOperation의 한 concrete class를 이용해서 태스크를 정의하고 Queue에 넣어주면, 시스템이 알아서 스케쥴링을 하고, 다 처리해 준다. 물론 스케쥴링을 어떻게 할지 프로그래머가 제어할 수있다.

자 여기서 OpenMP를 살펴보자. OpenMP는 NSOperation이 없었다면 참 많이 쓰였을 것이다. 아니 실제로 Leopard가 나오기 전엔 과학 기술 연산이나 고속 처리를 해야 하는 분야에서 많이 쓰였던 것으로 알고 있다. 이것은 플랫폼에 상관없이 C/C++ 컴파일러에 들어간 기능이기 때문에, porting이 쉬운 코드를 만들 수있다. 이런 OpenMP의 장점은 다음과 같다.

우선 single thread 코드를 쉽게 multi-thred 코드로 바꿀 수있다. 기본적으로 코딩은 single thread를 염두에 두고 한다. 그리고 몇개의 쓰레드를 만들 것인지 혹은 그것을 컴파일러가 알아서 정하게 만들 것인지 등을 간단한 키워드 조합을 통해서 정해주면 그게 multi-thread 코드가 된다. 디버깅을 하다보면 종종 기본 알고리즘이 확실하게 돌아가는지 확인하기 위해서 single thread 모델로 먼저 확인하고 싶을 경우가 있다. 근데 기존의 pthread를 이용해서 할때는, single thread와 multi-thread의 코드가 확 달라질 수가 있기 때문에, 이게 쉽지 않은 일이다. 하지만 OpenMP를 이용하면 무척 쉽게 전이를 할 수가 있고, 심지어 쓰레드를 두개를 쓸지, 세개를 쓸지 쉽게 바꾸고 속도를 비교해 보기가 쉽다.

그럼 이런 편하고 좋은 것을 두고서 Apple은 왜 NSOperation등을 준비했을까? 공부하기 힘들게.. OpenMP는 일단 몇개의 쓰레드로 돌아가기 시작하면 adaptive하지 않고 그냥 그 개수의 쓰레드로 돌아간다. 현재 내가 작업하는 코드를 보면, (그냥 MFC/Win32 API로 쓰레딩을 하는데), 뭔가로 바쁠 때, 파일 전송등을 하면 파일 전송이 아예 안되어 버리고 lag이 걸려 버리는 경우가 왕왕있다. 이게 참 문제여서, 하다 못해 그 프로그램에 로그인 하는 속도도 무척 느려져 버린다. 근데 NSOperation/Queue와 같은 것은, 그때 그때 시스템이 적응을 하도록 해 준다.

자 그럼 여기서 더 나가 보도록 하자. 바로 block과 Grand Central이다. Grand Central과 관련된 method들은 다(?) block을 사용하도록 되어  있다. 도대체 block이 뭐길래? 아무리 봐도 block은 Grand Central을 위해서 Apple이 다른 언어들로부터 차용해서 Obj-C에 넣은 것으로 보인다. block을 쉽게 이해하는 방법은, function pointer로 이해하는 것이다. 실지로 이 점에 대해서도 Apple의 문서에 나와 있고, 많이들 function pointer가 있는데 도대체 왜 block이란 새로운 것을 만들었냐고 궁금해들 하는 것 같다.우선 나 자신이 그렇다.

그럼 이 시점에서 block을 잘 살펴보자. 가만히 보면 얘는 fucntion object라고도 불린다는 것이 이해가 된다. 즉 function은 같은 code를 공유하는 것이라면, block은 한 카피씩 그 함수를 정의하는 객체가 생긴다. 즉 프로세서들에 분산을 할때, 이런 객체를 한 CPU에 하나씩 싹 던져 줄 수가 있다. function pointer는 기본적으로 같은 코드를 공유하기 때문에, synchronization이 필요하다. 즉 같은 메모리 영역을 억세스하니까, 순서대로 접근해야 하고, 특정 변수에 접근 할때는 exclusive하게 접근해야 한다. 근데 이 block은 새로운 copy가 만들어지는 것이니까, 그다지 synchronization을 생각하지 않아도 된다. 사실 이 말은 꼭 정확하진 않다. 이를테면 _block으로 정의된 변수는 read/write가 가능하므로 주의를 하긴 해야 한다. 단 이것도 가만히 보면 카피를 일단 하고, 나중에 원 소스에 반영하는 식 같아 보이던데,  그렇다면 그다지 synchronization에 상관을 안해도 되겠다. 혹 특별한 상황이 되면 필요할지는 모르겠다. 하지만 전반적으로 볼때, lock같은 것에 신경 안쓰고 코딩이 가능할 거라는 소리다. 이 점이 Grand Central같은 것을 구현할때, 있으면 무척 편할 building block이 아니겠는가?

자 이렇게 대충이나마 Snow Leopard에서 제공하는 멀티 태스킹에 사용되는 장치들에 대해서 생각해 보았다. 이들의 장점과 단점을 알고 쓰면 좋겠다.

근데 점점 NSOperation이나 Grand Central을 쓰면 플랫폼에 독립적인 코드는 짤 수가 없게 된다는 점도 인식하자. 아무리 Grand central이 Open Source화 되었어도, 그것을 쓰기 위한 Cocoa 함수/클래스가 다른 플랫폼에서도 쓸 수있는건 아니지 않는가?

Visual Studio 2010엔 Apple의 이런 기술에 대응하는 흡사한 기능이 들어가는 것으로 알고 있다. 이러나 저러나 프로그래머들만 죽어난다. 프로그래머는 집에 와서도 이런 것을 공부하고 있어야 하니까.. 언제 좀 쉬어 보나..

One response to this post.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: