비교할 문제
GIL과 CPU 병렬 처리에서 확인할 질문은 pure Python CPU 작업에서 thread와 process가 core를 사용하는 방식은 어떻게 다른가입니다. 같은 결과, timeout, 취소와 오류 전파를 맞추고 process 후보에는 serialization과 startup 비용을 포함합니다. 결과값, 순서, 오류 처리와 부수 효과가 다르면 같은 성능 비교가 아닙니다. 먼저 두 후보가 같은 일을 하는지 작은 입력으로 확인합니다.
원인 분석과 재현
CPython의 GIL은 한 process의 Python bytecode 병렬 실행을 제한하지만 I/O 대기와 GIL을 해제하는 native code의 동작은 다릅니다.
CPU 계산과 blocking I/O를 분리하고 payload 1KB·10MB, 작업 10개·1,000개에서 wall time, CPU time, RSS와 context switch를 봅니다. runtime version, 운영체제, CPU와 memory 제한도 결과와 함께 기록합니다.
비교 질문은 pure Python CPU 작업에서 thread와 process가 core를 사용하는 방식은 어떻게 다른가입니다.
| 후보 | 유리한 조건 | 함께 치르는 비용 |
|---|---|---|
| thread | GIL을 놓는 I/O·native extension | pure Python CPU 작업은 core 활용이 제한될 수 있음 |
| process | 격리된 CPU-bound Python 작업 | startup, IPC, pickle과 memory 복제 비용 |
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from time import perf_counter
def cpu_work(value: int) -> int:
return sum(i * i for i in range(value))
def run(executor_type, values):
started = perf_counter()
with executor_type(max_workers=4) as executor:
result = list(executor.map(cpu_work, values))
return perf_counter() - started, sum(result)
if __name__ == "__main__":
values = [200_000] * 20
for executor_type in (ThreadPoolExecutor, ProcessPoolExecutor):
print(executor_type.__name__, run(executor_type, values))
해결 방안과 판정
timeit.repeat() 결과 전체를 보관하고, 작은 코드 조각의 하한을 볼 때는 공식 문서가 안내하는 최솟값도 함께 확인합니다. 서비스 지연을 재는 경우에는 median과 p95를 별도로 기록합니다. CPU time, allocation, RSS·heap, GC, I/O 중 이 작업에 직접 관련된 지표만 선택합니다. 이름만 바꾼 concurrency benchmark는 의미가 없습니다. 실제 callable이 GIL을 놓는지와 payload 전송 비용을 포함해야 합니다.
차이가 반복 실행의 흔들림보다 작다면 더 단순하고 읽기 쉬운 구현을 선택합니다. 차이가 충분히 크더라도 실제 요청 전체의 지연과 자원 사용이 개선되는지 확인한 뒤 적용합니다.
공식 문서와 적용 범위
문서의 기본값과 동작은 version에 따라 달라질 수 있습니다. 현재 Python 구현과 version, dependency, 입력 크기와 실행 환경에서 다시 확인합니다.