[python] parallelize (multi-processing library Testing)

songmoana·2024년 3월 6일

Python code parallelize ( multi-threading vs multi-processing)

# multi-threading ?
	한 process안에 하나 이상의 thread를 구축하여 병렬 수행하는 것.
    (동일한 프로세스 내에서 메모리를 공유하고, 데이터공유와 통신이 가능)
    
    * 장점 )  작은 작업 단위로 생성되므로 스레드 간의 전환 및 통신이 빠르다.
    	     같은 프로세스 내에서 메모리를 공유하기 때문에 데이터 공유가 편리
             스레드 간의 컨텍스트 전환이 프로세스 간의 전환보다 적은 오버헤드를 가짐
             
    * 단점 ) Cpython에서는 GIL 이 존재해서 하나의 스레드만 파이썬 코드를 실행 가능
            -> I/O 작업에서는 영향이 거의 없지만 CPU-Bound 작업에서는 성능 향상이 덜 될 수 있다.
    

# multi-processing ? 

	각각의 process를 생성하여 작업을 실행

	* 장점 ) 각각의 process는 독립적인 파이썬 인터프리터를 가지고 있기 때문에 GIL 을 우회한다.
    	    독립적인 메모리 공간을 가지므로, 병렬 처리 시 데이터 공유에 대한 복잡성이 낮다.
            여러 CPU 코어를 활용하여 병렬 처리를 수행할 수 있음.
            	* 예 ) core 8개 CPU -> 8개의 process를 생성하여 작업 수행
    
    * 단점 ) 각각의 독립적인 메모리 공간을 가지므로, 메모리 사용량이 늘어날 수 있다.
    		프로세스 간의 전환은 오버헤드가 크므로 I/O 작업에서는 비효율적일 수 있다.
        	-> 역직렬화를 통해 데이터를 받아와야하므로 큰 오버헤드 발생

summarize

* I/O - multi threading
* CPU-bound - multi processing

parallelize TEST

  1. Origin
  2. python 표준 라이브러리 multiprocessing
  3. Ray 라이브러리 - 전체 코드 수정 불필요, 대시보드 * API 지원

  1. Dask 라이브러리 - numpy 배열, pandas df, scikit learn 호환
  2. Dispy 라이브러리
  3. Pandarallel 라이브러리 - pandas df 연산
  4. Ipyparallel 라이브러리
  5. Joblib 라이브러리 - numpy 배열, scikit learn 호환


0. common function (공통으로 사용될 연산 함수)

아래의 연산 함수를 500번 반복

import os
  
def cpu_bound_tutorial():
    result = 0
    for i in range(4000000):
        result += i
    return os.getpid() # pid 확인

TEST 조건
1) multi processing이 아닌 단일 프로세스로 공통 함수 실행
2) 각종 multi processing 라이브러리를 통해 공통 함수 실행
3) 누적 시간 확인 후 속도가 빠른 라이브러리 확인

1. origin (multi-threading 없는 순수 함수) - 약 53s

import time
from tqdm import tqdm
from common_func import cpu_bound_tutorial, show_result


def main(count):

    # checking time
    init = time.time()

    # run process 
    results_pid = [cpu_bound_tutorial() for _ in tqdm(range(count))]
    elapsed_time = time.time() - init

    # show results
    show_result("Origin", results_pid, elapsed_time)
    

2. multiprocessing (python 표준 라이브러리) - 약 7.54s


import time
import multiprocessing
from tqdm import tqdm
from common_func import cpu_bound_tutorial, show_result


def main(count):
    
    # # checking time
    init = time.time()
    
    # run process
    with multiprocessing.Pool(8) as pool:
        results = pool.starmap(cpu_bound_tutorial, [() for _ in tqdm(range(count))])
        
    elapsed_time = time.time() - init
    
    # # show results
    show_result("Multi-Processing", results, elapsed_time)

3. ray - 약 7.9s

import os
import ray
import time
from tqdm import tqdm
from common_func import show_result


def main(count):

    # init setting
    ray.init(num_cpus=8) # core 8

    # checking time
    init = time.time()

    @ray.remote   # 병렬 처리가 필요한 함수에 데코레이터만 붙이면 됨.
    def cpu_bound_tutorial():
        result = 0
        for i in range(4000000):
            result += i
        return os.getpid()

    result_ids = [cpu_bound_tutorial.remote() for _ in tqdm(range(count))]

    # run process and return results
    result = ray.get(result_ids)
    elapsed_time = time.time() - init
    
    # show results
    show_result("Ray", result, elapsed_time)

    ray.shutdown()

결과

* 복잡한 연산에서는 병렬처리를 하지 않은 원본 코드와 병렬처리를 한 코드의 속도 차이가 약 7배이상인 것을 확인할 수 있었다.
* 간단한 I/O 작업에서는 원본코드와 병렬코드의 차이가 거의 보이지 않았다. 
* 이미지 HTTP 통신 코드 test 시 원본 10s, ray 6.08s, multi-processing 4.8s.
* ray 와 multi-processing은 큰 차이가 없었고, multi-processing의 속도가 더 빨랐으나 ray 라이브러리의 장점인 전체 코드를 수정하지 않아도 된다는 점 때문에 
  유의미한 속도 차이가 없다면 ray 라이브러리를 쓰는 것도 나쁘지 않을듯.
* 이미지 전처리(numpy 연산)가 수행되는 코드에 병렬처리를 한다면 4-8 라이브러리 테스트를 해봐도 좋을 듯.
profile
옹모아나 - 개발백과

0개의 댓글