# multi-threading ?
한 process안에 하나 이상의 thread를 구축하여 병렬 수행하는 것.
(동일한 프로세스 내에서 메모리를 공유하고, 데이터공유와 통신이 가능)
* 장점 ) 작은 작업 단위로 생성되므로 스레드 간의 전환 및 통신이 빠르다.
같은 프로세스 내에서 메모리를 공유하기 때문에 데이터 공유가 편리
스레드 간의 컨텍스트 전환이 프로세스 간의 전환보다 적은 오버헤드를 가짐
* 단점 ) Cpython에서는 GIL 이 존재해서 하나의 스레드만 파이썬 코드를 실행 가능
-> I/O 작업에서는 영향이 거의 없지만 CPU-Bound 작업에서는 성능 향상이 덜 될 수 있다.
# multi-processing ?
각각의 process를 생성하여 작업을 실행
* 장점 ) 각각의 process는 독립적인 파이썬 인터프리터를 가지고 있기 때문에 GIL 을 우회한다.
독립적인 메모리 공간을 가지므로, 병렬 처리 시 데이터 공유에 대한 복잡성이 낮다.
여러 CPU 코어를 활용하여 병렬 처리를 수행할 수 있음.
* 예 ) core 8개 CPU -> 8개의 process를 생성하여 작업 수행
* 단점 ) 각각의 독립적인 메모리 공간을 가지므로, 메모리 사용량이 늘어날 수 있다.
프로세스 간의 전환은 오버헤드가 크므로 I/O 작업에서는 비효율적일 수 있다.
-> 역직렬화를 통해 데이터를 받아와야하므로 큰 오버헤드 발생
* I/O - multi threading
* CPU-bound - multi processing
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 라이브러리 테스트를 해봐도 좋을 듯.