2015년 7월 17일 금요일

쉬는 자원을 활용하기 위한 파이톤 멀티프로세싱 요약

멀티프로세싱에 대해서,


데이터가 양적으로 빅(BIG) 해지면, 수행 성능의 차이를 비율로 바라보는 센스가 필요하다.
가령 10초 걸리는 일을 1초에 한다는 것은, 10년 걸리는 일을 1년에 한다는 멋진 일이다.

성능 문제의 대부분은 고가의 장비로 해결 가능하지만, 가난한 우리들은 있는 자원을 최대한 활용해야 한다.
최근 CPU 집약적인 많은 양의 원시 데이터를 파싱할때, 시간을 비약적으로 줄인 파이톤 멀티프로세싱을 소개한다.

쉬는 자원을 활용하기 위한 멀티프로세싱의 구현 I

  • 할당 받은 가상 코어가 MAX 4개라면, 1개를 여유로 두고, 3개를 자유롭게 쓰겠다.
  • 작업 성격에 따라 검토해야 겠지만, 일반적으로 MAX - 1은 추천되는 방식이다.
In [1]:
from multiprocessing import Process, Queue
from datetime import datetime
import time

def f(task):
    time.sleep(2)
    print ("[ task id : %d - print time : %s ]" % (task,datetime.now().strftime('%s')))

if __name__ == '__main__':
    MAX_CORE = 4
    TASKS = 10
    for i in range(1,TASKS+1):
        p = Process(target=f, args=(i,))
        p.start()
        if i % ( MAX_CORE - 1 ) == 0:
            p.join()
    p.join()
[ task id : 1 - print time : 1437141542 ]
[ task id : 2 - print time : 1437141542 ]
[ task id : 3 - print time : 1437141542 ]
[ task id : 4 - print time : 1437141544 ]
[ task id : 5 - print time : 1437141544 ]
[ task id : 6 - print time : 1437141544 ]
[ task id : 7 - print time : 1437141546 ]
[ task id : 8 - print time : 1437141546 ]
[ task id : 9 - print time : 1437141546 ]
[ task id : 10 - print time : 1437141548 ]

쉬는 자원을 활용하기 위한 멀티프로세싱의 구현 II - Pool 사용

  • 동시 수행을 위한 프로세스 풀을 만들어 놓고 사용한다.
  • 필자 같은 초짜도 쉽게 수행할 수 있는 간략한 방법을 제공한다.
In [2]:
from multiprocessing import Pool

def f(task):
    time.sleep(2)
    return ("[ task id : %d - print time : %s ]" % (task,datetime.now().strftime('%s')))

if __name__ == '__main__':
    MAX_CORE = 4
    TASKS = 10
    with Pool(processes=MAX_CORE-1) as pool:
        out =pool.map(f,range(1,TASKS+1))
    for i in out: print(i)
[ task id : 1 - print time : 1437141553 ]
[ task id : 2 - print time : 1437141553 ]
[ task id : 3 - print time : 1437141553 ]
[ task id : 4 - print time : 1437141555 ]
[ task id : 5 - print time : 1437141555 ]
[ task id : 6 - print time : 1437141555 ]
[ task id : 7 - print time : 1437141557 ]
[ task id : 8 - print time : 1437141557 ]
[ task id : 9 - print time : 1437141557 ]
[ task id : 10 - print time : 1437141559 ]

멀티프로세싱 모니터링 방법

실무적으로는 nmon 같은 실시간 리소스 체크 툴을 사용하고,
리포팅을 위해서는 psutil같은 유용한 라이브러리를 활용하자.
In [3]:
import psutil
import pandas as pd

def cpu_usage_to_df(sec):
    cpus = []
    for i in range(1,sec + 1):
        cpus.append(psutil.cpu_percent(interval=1, percpu=True))
        if i % 30 == 0:
            print('elapsed %1.1f min' % (i / 60))
    return pd.DataFrame(cpus)
In [4]:
# 입력한 초 만큼의 초단위 모니터링 결과를 레코딩한다.
df = cpu_usage_to_df(60)
elapsed 0.5 min
elapsed 1.0 min
프로세스 POOL 3개를 지정하고, CPU 집약적인 다량의 파싱 작업을 수행했을때 1분 동안 모니터링을 돌렸다.
필자의 8코어 가상 서버에서 1분간의 모니터링 결과는 그림과 같이 매우 흡족하다.
In [34]:
df.plot(kind='area')
title('Stacked Area')
xlabel('time (second)')
ylabel('cpu usage (%)')
Out[34]:
<matplotlib.text.Text at 0x7f92fa0a62b0>
 CPU에 큰 병목현상이 있는 경우에는 코어 수 만큼의 성능 향상 효과가 있다.
이해를 돕기 위해서, Pool이 1/4/7/10의 경우에 1분간 각 코어의 평균 사용량을 조사 했다.
오랜지 세로 라인은 산술적인 최대 지점과 수행 평균과의 유격을 표현해 보았다.

  • 산술적인 최대 지점 = 지정 Pool 수 / 전체 CORE 수 * 100
  • 수행 평균 = 각 CORE의 사용량(%)의 합 / 전체 CORE 수

% 다른 외부 환경을 완전히 통제한 상황은 아니지만, 작업이 없을때 1% 이하의 사용량을 보인다. %

보통의 케이스에 Pool 1개의 상황을 상상하면서, 농을 피우는 녀석들이 있는지 가끔 확인하자.
모든 프로세스를 활용하는 상황에서도 기다릴 수 없다면, 서버 간에 처리가 가능한 분산 프로그래밍을 고려하자. (귀도 형과 함께라면 이 또한 심플하리라.)

진정으로 빅(BIG)한 상황이 오면, 하둡이나 DW 솔루션들이 당신을 기다리고 있다.
초기 도입 비용이나 시스템 운영 관리가 힘든 상황이라면,
사용한 만큼만 내는 AWS(아마존웹서비스)가 당신의 파트너가 되어 줄 것이다.


0 개의 댓글:

댓글 쓰기