python - sirve - Rendimiento de subprocess.check_output vs subprocess.call
subprocess python windows (2)
Al leer los documentos, subprocess.call
y subprocess.check_output
son casos de uso de subprocess.Popen
. Una pequeña diferencia es que check_output
generará un error de Python si el subproceso devuelve un estado de salida distinto de cero. La mayor diferencia se enfatiza en el bit sobre check_output
(mi énfasis):
La firma de la función completa es en gran medida la misma que la del constructor de Popen, excepto que no se permite la salida estándar, ya que se utiliza internamente . Todos los demás argumentos proporcionados se pasan directamente al constructor de Popen.
Entonces, ¿cómo es stdout
"utilizado internamente"? Comparemos call
y check_output
:
llamada
def call(*popenargs, **kwargs):
return Popen(*popenargs, **kwargs).wait()
check_output
def check_output(*popenargs, **kwargs):
if ''stdout'' in kwargs:
raise ValueError(''stdout argument not allowed, it will be overridden.'')
process = Popen(stdout=PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd, output=output)
return output
comunicar
Ahora tenemos que mirar también a Popen.communicate
. Al hacer esto, notamos que para una tubería, la communicate
hace varias cosas que simplemente toman más tiempo que simplemente devolver Popen().wait()
, como lo hace la call
.
Por un lado, communicate
procesos stdout=PIPE
si configura shell=True
o no. Claramente, la call
no lo hace. Simplemente permite que su shell emita lo que sea ... lo que lo convierte en un riesgo de seguridad, como lo describe Python aquí .
En segundo lugar, en el caso de check_output(cmd, shell=True)
(solo una canalización) ... cualquier subproceso que se envíe a la _communicate
se procesa mediante un subproceso en el método _communicate
. ¡Y Popen
debe unirse al hilo (esperar en él) antes de esperar, además, a que el subproceso termine!
Además, más trivialmente, procesa stdout
como una list
que luego debe unirse en una cadena.
En resumen, incluso con argumentos mínimos, check_output
pasa mucho más tiempo en los procesos de Python que la call
.
He estado usando subprocess.check_output()
durante algún tiempo para capturar la salida de los subprocesos, pero me encontré con algunos problemas de rendimiento en ciertas circunstancias. Estoy ejecutando esto en una máquina RHEL6.
El entorno de Python de llamada es compilado por Linux y de 64 bits. El subproceso que estoy ejecutando es un script de shell que finalmente desencadena un proceso python.exe de Windows a través de Wine (por eso, esta locura es otra historia). Como entrada para el script de shell, estoy ingresando una pequeña cantidad de código Python que se pasa a python.exe.
Si bien el sistema tiene una carga moderada / pesada (40 a 70% de utilización de la CPU), he observado que el uso de subprocess.check_output(cmd, shell=True)
puede provocar un retraso significativo (hasta ~ 45 segundos) después del subproceso ha finalizado la ejecución antes de que se devuelva el comando check_output. Mirar la salida de ps -efH
durante este tiempo muestra el subproceso llamado como sh <defunct>
, hasta que finalmente regresa con un estado de salida de cero normal.
Por el contrario, usar subprocess.call(cmd, shell=True)
para ejecutar el mismo comando bajo la misma carga moderada / pesada hará que el subproceso regrese inmediatamente sin demora, toda la salida impresa a STDOUT / STDERR (en lugar de devuelta por la función) llamada).
¿Por qué hay un retraso tan significativo solo cuando check_output()
está redirigiendo la salida STDOUT / STDERR a su valor de retorno, y no cuando la call()
simplemente lo imprime de nuevo a STDOUT / STDERR del padre?
Veamos el código. El .check_output tiene la siguiente espera:
def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
_WNOHANG=os.WNOHANG, _os_error=os.error, _ECHILD=errno.ECHILD):
"""Check if child process has terminated. Returns returncode
attribute.
This method is called by __del__, so it cannot reference anything
outside of the local scope (nor can any methods it calls).
"""
if self.returncode is None:
try:
pid, sts = _waitpid(self.pid, _WNOHANG)
if pid == self.pid:
self._handle_exitstatus(sts)
except _os_error as e:
if _deadstate is not None:
self.returncode = _deadstate
if e.errno == _ECHILD:
# This happens if SIGCLD is set to be ignored or
# waiting for child processes has otherwise been
# disabled for our process. This child is dead, we
# can''t get the status.
# http://bugs.python.org/issue15756
self.returncode = 0
return self.returncode
El .call espera usando el siguiente código:
def wait(self):
"""Wait for child process to terminate. Returns returncode
attribute."""
while self.returncode is None:
try:
pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
except OSError as e:
if e.errno != errno.ECHILD:
raise
# This happens if SIGCLD is set to be ignored or waiting
# for child processes has otherwise been disabled for our
# process. This child is dead, we can''t get the status.
pid = self.pid
sts = 0
# Check the pid and loop as waitpid has been known to return
# 0 even without WNOHANG in odd situations. issue14396.
if pid == self.pid:
self._handle_exitstatus(sts)
return self.returncode
Tenga en cuenta que el error relacionado con internal_poll. Se puede ver en http://bugs.python.org/issue15756 . Casi exactamente el problema que está encontrando.
Edición: El otro problema potencial entre .call y .check_output es que .check_output realmente se preocupa por la entrada estándar y la salida estándar e intentará realizar IO contra ambas canalizaciones. Si se está ejecutando en un proceso que se convierte en un estado zombie, es posible que una lectura en contra de una tubería en un estado inactivo esté causando el bloqueo que está experimentando.
En la mayoría de los casos, los estados de zombis se limpian con bastante rapidez, pero no lo harán si, por ejemplo, se interrumpen durante una llamada al sistema (como leer o escribir). Por supuesto, la llamada del sistema de lectura / escritura debería interrumpirse tan pronto como ya no se pueda realizar el IO, pero es posible que esté llegando a algún tipo de condición de carrera en la que las cosas se maten en un mal orden.
La única forma en que puedo pensar para determinar cuál es la causa en este caso es que usted agregue un código de depuración al archivo de subproceso o que invoque al depurador de Python e inicie un retroceso cuando se ejecuta en la condición que está experimentando.