12 puntos por darjeeling 2026-01-31 | Aún no hay comentarios. | Compartir por WhatsApp

Resumen:

  • Durante los últimos 15 años, el módulo subprocess de Python y la biblioteca psutil han usado un enfoque ineficiente de “sondeo busy-loop” al esperar la finalización de procesos (wait()), repitiendo sleep y waitpid.
  • Este enfoque provoca activaciones innecesarias de la CPU, consumo de batería, latencia en la detección de la finalización del proceso y mala escalabilidad al monitorear muchos procesos.
  • Con una actualización reciente, en Linux se implementó una verdadera “espera basada en eventos” usando pidfd_open() y poll(), y en BSD/macOS usando kqueue().
  • Windows ya usaba WaitForSingleObject, por lo que no hay cambios allí, pero en sistemas POSIX se eliminan los cambios de contexto innecesarios y el uso de CPU converge a “0”.

Resumen detallado:
1. Un problema que duró 15 años: el sondeo busy-loop
Desde que en Python 3.3 se agregó el parámetro timeout a subprocess.Popen.wait(), la biblioteca estándar de Python y la muy utilizada biblioteca psutil han estado usando un método ineficiente para esperar la finalización de procesos.

La lógica anterior era simple, pero ineficiente:

  1. Verificar el estado del proceso con waitpid(WNOHANG) (no bloqueante)
  2. Si no terminó, hacer sleep() un momento (con exponential backoff)
  3. Volver al paso 1 y repetir
# Método anterior (código conceptual)  
import time, os  
  
def wait_busy(pid, timeout):  
    delay = 0.0001  
    while True:  
        # Verificar si el proceso terminó (polling)  
        if os.waitpid(pid, os.WNOHANG) == (pid, status):  
            return status  
        time.sleep(delay)  
        delay = min(delay * 2, 0.040) # aumentar el tiempo de espera hasta un máximo de 40 ms  
  

Este enfoque tiene tres desventajas críticas.

  • Activaciones de CPU: por más que aumente el tiempo de espera, el sistema tiene que despertarse periódicamente para revisar el estado, desperdiciando ciclos de CPU y consumiendo energía.
  • Latencia: inevitablemente hay una diferencia de tiempo entre el momento real en que termina el proceso y el momento en que se detecta al despertar de sleep.
  • Escalabilidad: en entornos de servidor donde hay que monitorear cientos o miles de procesos al mismo tiempo, este overhead crece rápidamente.

2. La solución: espera basada en eventos para sistemas POSIX
Todos los sistemas POSIX ofrecen mecanismos para detectar cambios de estado en descriptores de archivo (select, poll, epoll, kqueue). Recientemente, Python y psutil mejoraron su implementación para aprovechar esto en la detección de PID de procesos.

  • Linux: usa la syscall pidfd_open(), introducida en el kernel Linux 5.3 en 2019. Esta devuelve un descriptor de archivo que apunta al PID del proceso, el cual puede registrarse en poll() o epoll() para vigilar el evento de finalización del proceso. (Se agregó al módulo os desde Python 3.9).
  • BSD / macOS: usan el filtro EVFILT_PROC de la syscall kqueue() para monitorear eficientemente eventos de procesos.
  • Windows: ya soportaba espera basada en eventos mediante la API WaitForSingleObject, por lo que no hay cambios.

3. Mejora de rendimiento y resultados
Con este cambio, cuando se llama a wait(), el proceso pasa al estado de “interruptible sleep” desde la perspectiva del kernel. Es decir, espera silenciosamente en espacio de kernel sin consumir absolutamente nada de CPU, y se despierta de inmediato cuando llega la señal de finalización del proceso.

Según benchmarks realizados con /usr/bin/time -v y otras herramientas, en comparación con el método anterior se redujeron drásticamente los cambios de contexto innecesarios, y también mejoró de forma inmediata la velocidad de detección de finalización del proceso. Esta actualización ya se incorporó a la biblioteca psutil y al core de CPython, por lo que los desarrolladores de Python podrán beneficiarse de esta mejora de rendimiento en el futuro sin tener que modificar su código.

Aún no hay comentarios.

Aún no hay comentarios.