파이썬 프로그램을 작성하다보면 성능의 벽에 부딪히게 되는데 코드 최적화를 수행해도 필요 수준보다 느릴 수 있다.
그렇다고, C언어로 작성하기에 코드 복잡도가 올라가고 한 부분만 C로 작성하는 것도 쉬운 것은 아니다.
그래서, CPython or SWIG or CLIF와 같은 도구를 활용하면 좋지만, 비용 문제에서 벗어날 수 없다
해결책은 concurrent.futures 내장 모듈을 사용하여 multiprocessing 내장 모듈이 정확히 맞아 떨어지게 한다.
#mymodule.py
def gcd(pair):
a,b = pair
low = min(a,b)
for i in range(low, 0,-1):
if a % i == 0 and b % i == 0:
return i
assert False. '도달할 수 없음'
#run_serial.py
import my_module
import time
NUMBERS = [
(1963309, 2265973) , (2030677.3814172)
]
def main():
start = time.time()
results = list(map(my_module.gcd, NUMBERS))
end = time.time()
delta = end - start
print(f' 총 {delta:.3f} 초 걸림')
if __name__ == '__main__':
main()
총 0.911초 걸림
#run_threads.py
import my_module
from concurrent.futures import ThreadPoolExecutor
import time
NUMBERS = [
...
]
def main():
start = time.time()
pool = ThreadPoolExecutor(max_workers=2)
results = list(pool.map(my_module.gcd, NUMBERS))
end = time.time()
delta = end - start
print(f' 총 {delta:.3f} 초 걸림')
if __name__ == '__main__':
main()
총 1.436초 걸림
#이제 코드 한 줄만 바꾸면 속도가 빨라진ㄷ
#concurrent.futures 모듈에 있는 ThreadPoolExecutor를 같은 모듈의 ProcessPoolExecutor로 바꾸면
#프로그램 속도가 빨라진다.
##run_parallel.py
import my_module
from concurrent.futures import ProcessPoolExecutor
import time
NUMBERS = [
...
]
def main():
start = time.time()
pool = ProcessPoolExecutor(max_workers=2) #이 부분만 바꿈
results = list(pool.map(my_module.gcd, NUMBERS))
end = time.time()
delta = end - start
print(f'총 {delta:.3f} 초 걸림')
if __name__ == '__main__':
main()
총 0.683 초 걸림
ProcessPoolExecutor 클래스가 multiprocessing 모듈이 제공하는 저수준 요소 활용
(부모) 이 객체(ProcessPoolExecutor 인스턴스)는 입력 데이터로 들어온 map 메서드에 전달된 NUMBERS의 각 원소를 취한다.
(부모) 이 객체는 1번에서 얻은 원소를 pickle모듈을 사용하여 이진 데이터로 직렬화한다.
(부모,자식)이 객체는 로컬 소켓을 통해 주 인터프리터 프로세스부터 자식 인터프리터 프로세스에게 2번에서 직렬화한 데이터를 복사한다.
(자식) 이 객체는 pickle를 사용해서 데이터를 파이썬 객체로 역직렬화한다.
(자식) 이 객체는 gcd함수가 들어있는 모듈을 임포트한다.
(자식) 이 객체는 입력 데이터에 대해 gcd함수를 실행한다. 이때 다른 자식 인터프리터 프로세스와 병렬로 실행한다.
(자식) 이 객체는 gcd함수의 결과를 이진 데이터로 직렬화한다.
(부모, 자식) 이 객체는 로컬 소켓을 통해 자식 인터프리터 프로세스부터 부모 인터프리터 프로세스에게 7번 직렬화한 결과 데이터를 돌려준다.
(부모) 이 객체는 데이터를 파이썬 객체로 역직렬화한다.
(부모) 여러 자식 프로세스가 돌려준 결ㄹ과를 병합해서 한 list로 만든다
CPU 병목 지점을 C 확장 모듈로 옮기면 파이썬에 투자한 비용을 최대한 유지하면서 프로그램 성능을 개선하는데 효과적일 수 있다.
그러나, C 확장 모듈로 옮기려면 많은 비용이 들고 포팅하는 과정에서 버그가 생길 수 있다.
multiprocessing 모듈을 사용하면 특정 유형의 파이썬 계산을 최소의 노력으로 병렬화할 수 있다.
concurrent.futures 내장 모듈이 제공하는 간단한 ProcessPoolExecutor클래스를 활용하면 multiprocessing의 능력을 최대한 활용
사용할 수 있는 모든 방법을 다 써보기 전에 multiprocessing이 제공하는 고급 기능을 시도하지 말라.