Python subprocess/psutil: 15 साल पुराने Busy-loop polling का अंत, अब सचमुच event-driven waiting
(gmpy.dev)सारांश:
- Python का
subprocessमॉड्यूल औरpsutilलाइब्रेरी पिछले 15 वर्षों से process termination का इंतज़ार (wait()) करते समयsleepऔरwaitpidको बार-बार दोहराने वाले अक्षम 'Busy-loop polling' तरीके का उपयोग करते रहे हैं। - यह तरीका अनावश्यक CPU wake-up, बैटरी खपत, और process termination detection latency जैसी समस्याएँ पैदा करता है, और बहुत सारे processes को मॉनिटर करते समय इसकी scalability भी खराब रहती है।
- हालिया अपडेट के साथ Linux में
pidfd_open()औरpoll(), जबकि BSD/macOS मेंkqueue()का उपयोग करते हुए वास्तविक 'event-driven waiting' लागू किया गया है। - Windows पहले से
WaitForSingleObjectका उपयोग करता है, इसलिए वहाँ कोई बदलाव नहीं है; लेकिन POSIX systems में अनावश्यक context switching हट जाता है और CPU उपयोग '0' के करीब पहुँच जाता है।
विस्तृत सारांश:
1. 15 साल से जारी समस्या: Busy-loop polling
Python 3.3 में subprocess.Popen.wait() में timeout parameter जोड़े जाने के बाद से, Python standard library और व्यापक रूप से उपयोग की जाने वाली psutil लाइब्रेरी process termination का इंतज़ार करने के लिए एक अक्षम तरीका इस्तेमाल करती रही हैं.
पुराना लॉजिक इस तरह सरल था, लेकिन अक्षम भी:
waitpid(WNOHANG)से process status जाँचें (non-blocking)- अगर process खत्म नहीं हुआ है, तो थोड़ी देर
sleep()करें (exponential backoff लागू) - फिर वापस चरण 1 पर जाएँ और दोहराएँ
# पुराना तरीका (conceptual code)
import time, os
def wait_busy(pid, timeout):
delay = 0.0001
while True:
# process termination की जाँच (polling)
if os.waitpid(pid, os.WNOHANG) == (pid, status):
return status
time.sleep(delay)
delay = min(delay * 2, 0.040) # अधिकतम 40ms तक waiting time बढ़ाना
इस तरीके की 3 गंभीर कमियाँ हैं।
- CPU Wake-ups: चाहे waiting time बढ़ा भी दिया जाए, system को समय-समय पर जागकर status जाँचना ही पड़ता है, जिससे CPU cycles बर्बाद होते हैं और power consumption बढ़ती है।
- Latency (विलंब): process के वास्तव में खत्म होने के समय और
sleepसे जागकर उसे detect करने के समय के बीच अनिवार्य समय-अंतर पैदा होता है। - Scalability (विस्तार-क्षमता): ऐसे server environments में जहाँ सैकड़ों या हजारों processes को एक साथ मॉनिटर करना पड़ता है, यह overhead बहुत तेज़ी से बढ़ता है।
2. समाधान: POSIX systems के लिए event-driven waiting
सभी POSIX systems file descriptor की state change detect करने के लिए mechanisms (select, poll, epoll, kqueue) उपलब्ध कराते हैं। हाल में Python और psutil को इस तरह बेहतर बनाया गया है कि वे इन्हें process PID detection के लिए उपयोग कर सकें।
- Linux: 2019 में Linux 5.3 kernel में जोड़े गए
pidfd_open()system call का उपयोग किया जाता है। यह process PID को इंगित करने वाला file descriptor लौटाता है, जिसेpoll()याepoll()में register करके process termination event को मॉनिटर किया जा सकता है। (osमॉड्यूल में Python 3.9 से उपलब्ध) - BSD / macOS:
kqueue()system call केEVFILT_PROCfilter का उपयोग करके process events को कुशलता से मॉनिटर किया जाता है। - Windows: वहाँ पहले से
WaitForSingleObjectAPI के जरिए event-driven waiting समर्थित थी, इसलिए कोई बदलाव नहीं है।
3. performance improvement और परिणाम
इस बदलाव के बाद wait() call के समय process, kernel के नज़रिए से 'Interruptible sleep' स्थिति में चला जाता है। यानी, CPU का बिल्कुल उपयोग किए बिना वह kernel space में शांतिपूर्वक इंतज़ार करता है, और process termination signal आते ही तुरंत जाग जाता है।
/usr/bin/time -v आदि से किए गए benchmarking में पाया गया कि पुराने तरीके की तुलना में अनावश्यक context switching नाटकीय रूप से घट गई, और process termination detection की गति भी तुरंत बेहतर हुई। यह अपडेट psutil लाइब्रेरी और CPython core में शामिल किया जा चुका है, इसलिए आगे Python developers बिना अलग code changes किए इस performance improvement का लाभ उठा सकेंगे.
अभी कोई टिप्पणी नहीं है.