배경
내 CPU가 6코어이기 때문에 이를 최대한 활용해보고 싶다.
ThreadPoolExecutor로 병렬실행 확인해보기
import concurrent.futures
import time
def long_running_task():
time.sleep(1)
def run_parallel_tasks(max_workers):
print(f"Running with {max_workers} workers...")
start_time = time.time() # 시작 시간 기록
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
# futures 객체 리스트를 생성
for _ in range(6):
executor.submit(long_running_task)
end_time = time.time() # 종료 시간 기록
print(f"Tasks completed in {end_time - start_time} seconds")
if __name__ == "__main__":
for workers in [1,2,3,4,5,6,8,10,15]:
run_parallel_tasks(workers)
결과
Running with 1 workers...
Tasks completed in 6.002362489700317 seconds
Running with 2 workers...
Tasks completed in 3.0017588138580322 seconds
Running with 3 workers...
Tasks completed in 2.0016236305236816 seconds
Running with 4 workers...
Tasks completed in 2.0018842220306396 seconds
Running with 5 workers...
Tasks completed in 2.0016212463378906 seconds
Running with 6 workers...
Tasks completed in 1.0014495849609375 seconds
Running with 8 workers...
Tasks completed in 1.0020780563354492 seconds
Running with 10 workers...
Tasks completed in 1.0015478134155273 seconds
Running with 15 workers...
Tasks completed in 1.0013375282287598 seconds해석 : 내 cpu 사양대로 6코어까지 실행시간이 줄어들다 포화된것을 확인할 수 있음
좀더 실전적인 예제로 병렬실행 확인
import concurrent.futures
import time
# CPU를 많이 사용하는 계산 작업을 수행하는 함수
def cpu_intensive_task():
result = 0
for i in range(1, 100000000):
result += i**2
# 주어진 max_workers 값에 따라 병렬 실행 및 시간 측정 함수
def run_parallel_tasks(max_workers):
print(f"Running with {max_workers} workers...")
start_time = time.time() # 시작 시간 기록
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
executor.submit(cpu_intensive_task)
end_time = time.time() # 종료 시간 기록
print(f"Tasks completed in {end_time - start_time} seconds\n")
if __name__ == "__main__":
for workers in [1,2,3,4,5,6,8,10,15]:
run_parallel_tasks(workers)
- 결과
Running with 1 workers...
Tasks completed in 7.927804946899414 seconds
Running with 2 workers...
Tasks completed in 7.661545038223267 seconds
Running with 3 workers...
Tasks completed in 7.6467390060424805 seconds
Running with 4 workers...
Tasks completed in 7.693345785140991 seconds
Running with 5 workers...
Tasks completed in 7.697876930236816 seconds
Running with 6 workers...
Tasks completed in 7.696856498718262 seconds
Running with 8 workers...
Tasks completed in 7.722061395645142 seconds
Running with 10 workers...
Tasks completed in 7.729133367538452 seconds
Running with 15 workers...
Tasks completed in 7.65252685546875 seconds
- 해석 : 6코어로 갈수록 시간이 줄어드는걸 볼 수 있었지만, 1/6의 속도는 아니었음.
- 원인 : 파이썬은 GIL을 사용하여 한 번에 하나의 스레드만이 Python 바이트코드를 실행할 수 있도록 합니다. 이는 멀티코어 시스템에서도 여러 스레드가 동시에 실행되는 것을 제한하여, CPU 바운드 작업에서 멀티스레딩의 성능 향상을 크게 제한할 수 있음
- 해결방안 : ProcessPoolExecutor사용하면 각각의 프로세스가 별도의 메모리 공간을 가지고 독립적으로 실행해, 진정한 의미의 병렬 실행가능.
ProcessPoolExecutor를 사용한 병렬 처리
import concurrent.futures
import time
# CPU를 많이 사용하는 계산 작업을 수행하는 함수
def cpu_intensive_task():
result = 0
for i in range(1, 100000000):
result += i**2
# 주어진 max_workers 값에 따라 병렬 실행 및 시간 측정 함수
def run_parallel_tasks(max_workers):
print(f"Running with {max_workers} workers...")
start_time = time.time() # 시작 시간 기록
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
executor.submit(cpu_intensive_task)
end_time = time.time() # 종료 시간 기록
print(f"Tasks completed in {end_time - start_time} seconds\n")
if __name__ == "__main__":
for workers in [1,2,3,4,5,6,8,10,15]:
run_parallel_tasks(workers)
Running with 1 workers...
Tasks completed in 0.30499911308288574 seconds
Running with 2 workers...
Tasks completed in 0.15899944305419922 seconds
Running with 3 workers...
Tasks completed in 0.14999914169311523 seconds
Running with 4 workers...
Tasks completed in 0.15317392349243164 seconds
Running with 5 workers...
Tasks completed in 0.16340088844299316 seconds
Running with 6 workers...
Tasks completed in 0.14251923561096191 seconds
Running with 8 workers...
Tasks completed in 0.1419992446899414 seconds
Running with 10 workers...
Tasks completed in 0.14299988746643066 seconds
Running with 15 workers...
Tasks completed in 0.14200210571289062 seconds
- 결과 : 결론적으로 ThreadPoolExecutor은 애초에 생각했던 병렬 실행은 아니었고 병렬 스레드였다(전혀 다른 개념). 실행속도를 볼때, ThreadPoolExecutor를 사용할때에 비해, ProcessPoolExecutor가 압도적으로 빨라진것을 확인할수 있었다. 진정한 의미의 병렬실행은 ProcessPoolExecutor였다. 그렇다면 ThreadPoolExecutor은 뭐고 언제 사용하는게 좋을까? 다음 포스트에서 알아보고자 한다.
'그때그때 CS 정리' 카테고리의 다른 글
나만의 코드 규칙 정립 (0) | 2024.06.30 |
---|---|
스레드와 프로세서의 구조적 차이 (0) | 2024.06.20 |
파이썬 병렬 스레드 연구 (0) | 2024.06.20 |
비동기 프로그래밍(asyn,aiohttp, aiofiles,aiomysql)은 뭘까? (0) | 2024.06.19 |
벡터 데이터베이스 (0) | 2024.06.18 |