2012-08-14 14 views
10

¿Es ventajoso llamar a funciones de registro con formato de cadena + lista de args frente a formato en línea?Registro idiomático de Python: formato de cadena + lista de args vs. formato de cadena en línea - ¿cuál es el preferido?

que he visto (y escrito) código de registro que utiliza en línea el formato de cadenas:

logging.warn("%s %s %s" % (arg1, arg2, arg3)) 

y sin embargo yo supongo que es mejor (en cuanto al rendimiento, y más idiomática) a utilizar:

logging.warn("%s %s %s", arg1, arg2, arg3) 

porque la segunda forma evita las operaciones de formateo de cadenas antes de invocar la función de registro. Si el nivel de registro actual filtra el mensaje de registro, no es necesario formatearlo, lo que reduce el tiempo de cálculo y las asignaciones de memoria.

¿Estoy en el camino correcto aquí o me he perdido algo?

+1

Creo que está en el camino correcto. – mgilson

+0

¿Tiene alguna referencia sobre el momento en que se realiza el formato de cadena en diferentes momentos? Hubiera esperado que ambos tuvieran lugar antes de que se realice la llamada a 'advertir'. No disputas lo que estás diciendo, solo curiosidad por saber más. – Levon

+0

Bueno, en la primera forma, estamos formateando cadenas (* y * creación de tuplas) * antes * de la llamada a 'logging.warn' - en el segundo caso, solo estamos pasando una lista de argumentos a' logging.warn' (que es simplemente una creación tuple?) - por lo que evitamos una operación de formateo de cadenas en la invocación. Como mencioné en la pregunta, supongo que si el nivel de registro actual filtrara el mensaje de registro, no se realizaría ningún formateo, evitando de nuevo la operación de formateo de cadenas. Mi conjetura es esta * debería * ahorrar tiempo de procesamiento y asignaciones de memoria. – Inactivist

Respuesta

14

En mi humilde opinión, para los mensajes que es muy probable que se muestren, como los dados a error o warn, no hace mucha diferencia.

Para los mensajes que se muestran con menos frecuencia, definitivamente me decantaría por la segunda versión, principalmente por motivos de rendimiento. A menudo doy grandes objetos como un parámetro al info, que implementa un costoso método __str__. Claramente, enviar esto preformateado a info sería un desperdicio de rendimiento.

ACTUALIZACIÓN

acabo de revisar el código fuente del módulo logging y, de hecho, el formato se realiza después de comprobar el nivel de registro. Por ejemplo:

class Logger(Filterer): 
    # snip 
    def debug(self, msg, *args, **kwargs): 
     # snip 
     if self.isenabledfor(debug): 
      self._log(debug, msg, args, **kwargs) 

Se puede observar que msg y args son intacta entre llamar log y comprobar el nivel de registro.

ACTUALIZACIÓN 2

Spired por Levon, quiero añadir algunas pruebas para objetos que tienen un método costoso __str__:

$ python -m timeit -n 1000000 -s "import logging" -s "logger = logging.getLogger('foo')" -s "logger.setLevel(logging.ERROR)" "logger.warn('%s', range(0,100))" 
1000000 loops, best of 3: 1.52 usec per loop 
$ python -m timeit -n 1000000 -s "import logging" -s "logger = logging.getLogger('foo')" -s "logger.setLevel(logging.ERROR)" "logger.warn('%s' % range(0,100))" 
1000000 loops, best of 3: 10.4 usec per loop 

En la práctica, esto podría dar un impulso bastante alto rendimiento.

+1

En otra ocasión, esto puede marcar la diferencia si el registro se encuentra en un ciclo de rendimiento crítico. –

+2

Eso es lo que esperaba basado en la observación. ¡Gracias por ir a bucear desde la fuente! – Inactivist

+0

Python 3.El formato de 6 cadenas de caracteres muestra la misma penalización de tiempo: 'python -m timeit -n 1000000 -s" registro de importación "-s" logger = logging.getLogger ('foo') "-s" logger.setLevel (logging.ERROR) "-s" x = list (rango (0,100)) "" logger.warn (f '{x}') "' produce 10 usos por ciclo, igual que con el formato de cadenas de estilo clásico. Pasar los parámetros de formateo directamente a la instrucción de registro produce 2.12 usec. –

6

En caso de que esto sea útil, aquí es una prueba rápida para la sincronización sólo las dos opciones de formato:

In [61]: arg1='hello' 
In [62]: arg2='this' 
In [63]: arg3='is a test' 

In [70]: timeit -n 10000000 "%s %s %s" % (arg1, arg2, arg3) 
10000000 loops, best of 3: 284 ns per loop 

In [71]: timeit -n 10000000 "%s %s %s", arg1, arg2, arg3 
10000000 loops, best of 3: 119 ns per loop 

parece dar el segundo enfoque el borde.

+0

¿Y qué hay de la llamada subsiguiente de '%' con la función 'log'? – Vladimir

+0

Entonces, la lección más importante que puedo aprender de su respuesta: ¡timeit es mi amigo! : D – Inactivist

+0

@Inactivist He encontrado que es útil ... a menudo para verificar (o contrastar) mis suposiciones sobre lo que es rápido y lo que no :) – Levon

2

Evitar la cadena de formato en línea hace ahorrar algo de tiempo si el nivel de registro actual filtra el mensaje de registro (como yo esperaba) - pero no mucho:

In [1]: import logging 

In [2]: logger = logging.getLogger('foo') 

In [3]: logger.setLevel(logging.ERROR) 

In [4]: %timeit -n 1000000 logger.warn('%s %s %s' % ('a', 'b', 'c')) 
1000000 loops, best of 3: 1.09 us per loop 

In [12]: %timeit -n 1000000 logger.warn('%s %s %s', 'a', 'b', 'c') 
1000000 loops, best of 3: 946 ns per loop 

Así que, como user1202136 pointed out, la diferencia de rendimiento global depende cuánto tiempo lleva formatear la cadena (lo que podría ser significativo dependiendo del costo de llamar a __str__ en los argumentos que se pasan a la función de registro).