2012-08-14 3 views
10

Ist es vorteilhaft, Logging-Funktionen mit Format String + Args List vs. Inline-Formatierung aufzurufen?Idiomatische Python-Protokollierung: Formatstring + Args-Liste vs. Inline-String-Formatierung - was wird bevorzugt?

Ich habe gesehen (und geschrieben) Logging-Code, die Inline-Formatierung von Zeichenfolge verwendet:

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

und doch gehe ich davon aus, es ist besser (Performance-weise, und mehr idiomatische) zu verwenden:

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

, weil die zweite Form vor dem Aufrufen der Protokollierungsfunktion Zeichenfolgenformatierungsoperationen vermeidet. Wenn die aktuelle Protokollierungsstufe die Protokollnachricht ausfiltert, ist keine Formatierung erforderlich, wodurch Rechenzeit und Speicherzuweisungen verringert werden.

Bin ich hier auf dem richtigen Weg, oder habe ich etwas verpasst?

+1

Ich denke, Sie sind auf dem richtigen Weg. – mgilson

+0

Haben Sie eine Referenz über das Timing der String-Formatierung, die zu unterschiedlichen Zeiten stattfindet? Ich hätte erwartet, dass beide vor dem Aufruf zu "warnen" stattfinden. Nicht bestreiten, was Sie sagen, nur neugierig, mehr zu erfahren. – Levon

+0

Nun, in der ersten Form machen wir String-Formatierung (* und * Tuple-Erstellung) * vor * zum Aufruf von 'logging.warn' - im zweiten Fall übergeben wir nur eine Liste von Argumenten an' logging.warn' (was nur Tupel-Erstellung ist?) - so vermeiden wir eine String-Formatierung beim Aufruf. Wie ich in der Frage erwähnt habe, nehme ich an, dass, wenn die aktuelle Protokollierungsstufe die Protokollnachricht herausfiltern würde, keine Formatierung durchgeführt würde, was wiederum die Formatierung der Zeichenfolgen vermeidet. Meine Vermutung ist dies * sollte * Verarbeitungszeit und Speicherzuweisungen speichern. – Inactivist

Antwort

14

IMHO, für Nachrichten, die sehr wahrscheinlich angezeigt werden, wie zum Beispiel error oder warn gibt es keinen großen Unterschied.

Für Nachrichten, die weniger wahrscheinlich angezeigt werden, würde ich definitiv für die zweite Version gehen, hauptsächlich aus Leistungsgründen. Ich gebe oft große Objekte als Parameter an info, die eine teure __str__ Methode implementieren. Das Senden dieser vorformatierten Datei an info wäre eindeutig eine Performanceverschwendung.

UPDATE

Ich habe gerade überprüft den Quellcode des logging Modul und in der Tat, Formatierung nach Überprüfung der Protokollebene erfolgt. Zum Beispiel:

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

Man kann beobachten, dass msg und args unberührt sind zwischen log und die Überprüfung der Protokollebene aufrufen.

UPDATE 2

Spired von Levon, lassen Sie mich einige Tests für Objekte hinzufügen, die eine teure __str__ Methode haben:

$ 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 

In der Praxis ist dies eine ziemlich hohe Performance-Schub geben könnte.

+1

Ein anderes Mal ist dies wahrscheinlich ein Unterschied, wenn die Protokollierung in einer leistungskritischen Schleife ist. –

+2

Das habe ich aufgrund von Beobachtungen erwartet. Danke für das Quellentauchen! – Inactivist

+0

Python 3.6 f-string-Formatierung zeigt die gleiche Zeitstrafe: "python -m time it -n 1000000 -s" import logging "-s" logger = logging.getLogger ('foo') "-s" logger.setLevel (logging.ERROR) "-s" x = list (range (0,100)) "" logger.warn (f '{x}') "ergibt 10 usec pro Schleife, wie bei der klassischen Formatierung von Zeichenketten. Die Übergabe der Formatierungsparameter direkt an die Protokollanweisung ergibt 2.12 usec. –

6

Falls dies hilfreich ist, hier ist ein kurzer Zeittest nur für die zwei Formatierungsoptionen:

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 

scheint den Rand dem zweiten Ansatz zu geben.

+0

Und was ist mit dem nachfolgenden Aufruf von '%' withing 'log' Funktion? – Vladimir

+0

Also, die wichtigste Lektion kann ich aus Ihrer Antwort lernen: Zeit ist mein Freund! : D – Inactivist

+0

@Inaktivist Ich habe festgestellt, dass es nützlich ist .. oft zu überprüfen (oder widersprechen) meine Annahmen über was ist schnell und was nicht :) – Levon

2

Inline-String-Formatierung Vermeiden dauert einige Zeit sparen, wenn die aktuelle Protokollebene die Protokollnachrichtenfilter (wie ich erwartet hatte) - aber nicht viel:

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 

So, wie user1202136 pointed out die Differenz Gesamtleistung hängt davon ab, wie lange es dauert, die Zeichenfolge zu formatieren (was je nach den Kosten für das Aufrufen von __str__ für Argumente, die an die Protokollierungsfunktion übergeben werden, erheblich sein kann)

Verwandte Themen