Python subprocess, 폴링 방식 탈피하고 성능 향상!
Python의 `subprocess` 모듈에서 프로세스 종료 대기 방식이 15년 만에 개선됨
기존의 busy-loop 폴링 방식 대신 `pidfd_open()` (Linux) 및 `kqueue()` (BSD/macOS)를 활용한 이벤트 기반 대기 방식 도입
`poll()` 및 `kqueue()`를 사용하여 CPU 사용률 감소 및 응답 속도 향상을 달성
psutil 및 CPython에 기여, 윈도우(Windows)는 이미 WaitForSingleObject를 통해 효율적인 대기 방식 적용
busy-loop 폴링 방식의 문제점
기존 `subprocess` 모듈은 `Popen.wait()`에서 busy-loop 폴링(Busy-loop Polling) 방식을 사용하여 프로세스 종료를 대기했다. 이는 프로세스 상태를 확인하기 위해 지속적으로 CPU를 깨우는 방식으로, CPU 사이클 낭비(CPU Cycle Waste)와 배터리 소모를 야기했다. 또한, 프로세스 종료 감지 지연 시간(Latency)과 여러 프로세스 동시 모니터링 시의 확장성 문제(Scalability Issue)를 발생시켰다.
이벤트 기반 대기 방식의 도입
새로운 접근 방식은 `pidfd_open()` (Linux) 및 `kqueue()` (BSD/macOS) 시스템 콜을 활용하여 이벤트 기반 대기(Event-driven Waiting)를 구현했다. `poll()` 또는 `kqueue()`를 사용함으로써, 프로세스는 종료 시점 또는 타임아웃 시점에 커널의 알림을 받게 되어, CPU 사용률을 대폭 감소(CPU Usage Reduction)시켰다. 이러한 방식은 `time.sleep()`과 유사하게, 프로세스를 인터럽트 가능한 슬립 상태(Interruptible Sleep State)로 만들어 CPU 자원 소모를 최소화한다.
성능 측정 및 CPython 기여
성능 측정 결과, busy-loop 폴링 방식에서는 다수의 컨텍스트 스위칭(Context Switching)이 발생했지만, 이벤트 기반 방식에서는 소수의 스위칭만 발생하여 성능 향상을 입증했다. psutil 구현에 이어 CPython `subprocess` 모듈에도 동일한 개선 사항이 적용되어, psutil 개발 경험이 Python 표준 라이브러리에 기여하는 두 번째 사례가 되었다. 이는 15년 전 `subprocess.Popen.wait()`의 타임아웃 파라미터 추가에 대한 상호 영감(Mutual Inspiration)의 결과이다.
대안 기술 및 고려 사항
커뮤니티에서는 `SIGCHLD` 시그널 핸들러를 활용한 방식과 `asyncio`의 self-pipe 트릭을 대안으로 제시했지만, 라이브러리 수준에서는 멀티 스레딩 환경(Multi-threading Environment)에서의 문제와 시그널 핸들러(Signal Handler)의 전역 변수 특성으로 인해 어려움이 있다고 지적했다. 또한, 윈도우(Windows)는 이미 `WaitForSingleObject`를 통해 효율적인 대기 방식을 사용하고 있어, 별도의 개선이 필요하지 않다는 점도 언급되었다.