바야흐로 초딩도 멀티코어로 오락하는 시대가 오면서 이제 파이썬 GIL 공포가
많은 사람들을 위협하고 있었습니다. 그에 대한 해결책으로 예전부터
병렬 프로그래밍을 위한 여러가지 프레임워크, 예를 들어
MPI, CORBA, PyRO 같은 것들이 나왔지만, 다들 멋있게 모든 걸 포용하는
라이브러리를 만들다 보니 설치가 어렵거나 배우는데 한참 걸리는 게 결국 문제가
돼서 실제로 심각한 개발자 아니면 그냥 “CPU 1개면 충분해요”라고 눈을 반짝거리며
스스로 최면을 걸고 있었던 것이 사실입니다~
그래서 GIL의 파이썬 프로젝트 자체에서의 해결책으로는 공식적으로 결론에 도달한
것은 아니지만 Adam Olsen의 GIL없애기 프로젝트 같은 것도 있었는데,
이번에 몇몇 사람들의 강력한 후원으로 파이썬 2.6과 3.0부터
pyProcessing이 새 이름 multiprocessing
으로 표준 라이브러리로 들어오게 되었습니다.
pyprocessing의 다른 병렬 처리 라이브러리들에 대한 장점은
뭐니뭐니해도 표준 threading 모듈과 API가 같다는 점이겠죠. threading으로 기존에 짜 놓은
프로그램을 그냥 모듈 이름과 클래스 이름 아주 약간만 바꿔주면 쓰레딩 대신 멀티프로세싱을
사용하게 되어서 결국에는 멀티코어를 제대로 쓰는 프로그램이 됩니다. 실제로
쓰레딩같이 모든 변수를 공유하는 것은 아니고, 리턴값만 전달을 받기 때문에
정확히는 좀 다르다고 볼 수도 있지만, 뭐 “그렇게 짜면 왕변태”라고 선언하면 되겠죠. ㅎㅎ;
그래서 실제로 쓰는 모양을 보려고 옛날에 유행했던 정규식으로 소수 검사를 하는 걸 한 번 돌려 봤습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<span class="k">from</span> <span class="nn">multiprocessing</span> <span class="k">import</span> <span class="n">Process</span> <span class="k">as</span> <span class="n">worker</span> <span class="k">import</span> <span class="nn">re</span><span class="o">,</span> <span class="nn">time</span> <span class="k">def</span> <span class="nf">isprime</span><span class="p">(</span><span class="n">n</span><span class="p">):</span> <span class="n">convert</span> <span class="o">=</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s">'1'</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="k">return</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">r'^1?$|^(11+?)\1+$'</span><span class="p">,</span> <span class="n">convert</span><span class="p">)</span> <span class="n">serial_st</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mf">10</span><span class="p">):</span> <span class="n">isprime</span><span class="p">(</span><span class="mf">1234567</span><span class="p">)</span> <span class="k">print</span> <span class="s">'Serial:'</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">serial_st</span> <span class="n">parallel_st</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="n">processes</span> <span class="o">=</span> <span class="p">[</span><span class="n">worker</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">isprime</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="mf">1234567</span><span class="p">,))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mf">10</span><span class="p">)]</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">]</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">]</span> <span class="k">print</span> <span class="s">'Parallel:'</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">parallel_st</span> |
보시면 threading으로 만든 모듈과 join, start, worker 등의 사용법이 같아서 그냥 쉽게 바꾸실 수 있는데, 이렇게 돌려보면 대략 시간이 듀얼코어에서는 이렇게 나옵니다.
1 2 |
Serial: 18.2321028709 Parallel: 8.54528999329 |
대략 2배 정도 빨라졌죠~ 그냥 고전적인 던져주고 실행해서 리턴받는 방식 말고 보통
실제로 더 많이 쓰는 Queue 모델로 일꾼을 코어 개수만큼 돌려보면 이렇게 할 수도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="k">from</span> <span class="nn">multiprocessing</span> <span class="k">import</span> <span class="n">Process</span><span class="p">,</span> <span class="n">Queue</span> <span class="k">import</span> <span class="nn">re</span><span class="o">,</span> <span class="nn">time</span> <span class="n">q</span> <span class="o">=</span> <span class="n">Queue</span><span class="p">()</span> <span class="k">def</span> <span class="nf">isprime</span><span class="p">(</span><span class="n">n</span><span class="p">):</span> <span class="n">convert</span> <span class="o">=</span> <span class="s">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s">'1'</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="k">return</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="s">r'^1?$|^(11+?)\1+$'</span><span class="p">,</span> <span class="n">convert</span><span class="p">)</span> <span class="k">def</span> <span class="nf">primeworker</span><span class="p">():</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span> <span class="n">n</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">get</span><span class="p">()</span> <span class="n">isprime</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">print</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">parallel_st</span> <span class="n">parallel_st</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="n">processes</span> <span class="o">=</span> <span class="p">[</span><span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">primeworker</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mf">2</span><span class="p">)]</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mf">10</span><span class="p">):</span> <span class="n">q</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="mf">1234567</span><span class="p">)</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">]</span> |
시간은 대략 9초 정도 듭니다. 그리고 fd 넘겨주기를 지원하는 플랫폼들에서는 소켓도
받아다가 넘길 수가 있으니 네트워크 프로그램도 간단하게 멀티코어를 쓸 수 있습니다.
아주 간단하게 멀티코어를 쓸 수 있는 장점으로 표준 라이브러리로 도입이 되긴 했지만,
아직 문제가 몇 개 있는데요. 대표적으로 FreeBSD에서는 아직 POSIX 1003.1b 세마포어를 “제대로” 지원하지 않기 때문에 FreeBSD에서는 Queue나 Lock등과 관련된 것들을
하나도 쓸 수가 없습니다. (위 예제는 그래서 리눅스에서 테스트할 수 밖에 없었습니다;;)
그 외에 MPI 등의 “심각한” 분산 API들을 쓰던 프로그래머들은 이게 애들 장난이냐 하면서 없는 기능들을 지적할 수도 있는데, 아무래도 표준 라이브러리로써 아주 간단하고
기초적인 기능만 제공하는 것이 목적이고, 셋업이 복잡하거나 거대 프레임워크를 끌고 다니는 경우라면 표준에서 안정적으로 관리하기가 힘들겠죠. 그래서 multiprocessing을
도입하자 주장한 개발자는 이 모듈은 절대 다른 분산 관련 모듈을 쓰지 말라는 의미가 아니고
같이 쓰면 더욱 좋다고 강조합니다.
예제 파일에다 그냥 붙여넣고 돌렸더니 윈도우가 사망하네요;
if __name__ == ‘__main__’: 안에 넣고 돌리니 되긴 하는데 Parallel이 더 느린.. 싱글코어의 비애 -0-
사용방법은 정말 간편하네요. 얼른 테스트 해봐야겠습니다.
좋은글 감사합니다. ^^
윈도에서도 쓸만할까요?
찾아보진 않았는데, OpenMP 관련된 얘기는 없나요?