2013-12-14 3 views
14

Ich habe gerade versucht, dieses Skript mit Python 3.3 auszuführen. Leider ist es etwa doppelt so langsam wie mit Python 2.7.Warum ist Drucken in Python 3.3 so langsam und wie kann ich es beheben?

#!/usr/bin/env python 

from sys import stdin 

def main(): 
    for line in stdin: 
     try: 
      fields = line.split('"', 6) 
      print(fields[5]) 
     except: 
      pass 

if __name__ == '__main__': 
    main() 

Hier sind die Ergebnisse:

$ time zcat access.log.gz | python3 -m cProfile ./ua.py > /dev/null 

real 0m13.276s 
user 0m18.977s 
sys  0m0.484s 

$ time zcat access.log.gz | python2 -m cProfile ./ua.py > /dev/null 

real 0m6.139s 
user 0m11.693s 
sys  0m0.408s 

Profilieren zeigt, dass die zusätzliche Zeit im Druck verbringen ist:

$ zcat access.log.gz | python3 -m cProfile ./ua.py | tail -15 
    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:1594(_handle_fromlist) 
    196806 0.234 0.000 0.545 0.000 codecs.py:298(decode) 
     1 0.000 0.000 13.598 13.598 ua.py:3(<module>) 
     1 4.838 4.838 13.598 13.598 ua.py:6(main) 
     1 0.000 0.000 13.598 13.598 {built-in method exec} 
     1 0.000 0.000 0.000 0.000 {built-in method hasattr} 
    4300456 4.726 0.000 4.726 0.000 {built-in method print} 
    196806 0.312 0.000 0.312 0.000 {built-in method utf_8_decode} 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
    4300456 3.489 0.000 3.489 0.000 {method 'split' of 'str' objects} 

$ zcat access.log.gz | python2 -m cProfile ./ua.py | tail -10 
    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 6.573 6.573 ua.py:3(<module>) 
     1 3.894 3.894 6.573 6.573 ua.py:6(main) 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
    4300456 2.680 0.000 2.680 0.000 {method 'split' of 'str' objects} 

Wie kann ich diesen Aufwand vermeiden? Hat es etwas mit UTF-8 zu tun?

+3

Nun, 'print' ist in python3 kein Statement mehr, daher wird ein Overhead erwartet. Dies könnte wahrscheinlich durch Verwendung von 'sys.stdout.write' reduziert werden - oder besser noch, indem Sie zuerst eine Liste Ihrer Strings erstellen und dann' sys.stdout.writelines' verwenden, vorausgesetzt, dass Speicher kein Problem ist. In beiden Fällen müssten Sie die von 'print' erstellten Zeilenumbrüche selbst hinzufügen. – l4mpi

Antwort

12

Python 3 decodiert Daten, die von stdin gelesen werden, und kodiert wieder für stdout; es ist nicht so sehr die print() Funktion, die hier langsamer ist als die Unicode-zu-Byte-Konvertierung und umgekehrt.

In Ihrem Fall möchten Sie dies wahrscheinlich umgehen und nur mit Bytes umgehen; Sie können die zugrunde liegenden BufferedIOBase Implementierung durch die .buffer attribute Zugang:

from sys import stdin, stdout 

try: 
    bytes_stdin, bytes_stdout = stdin.buffer, stdout.buffer 
except AttributeError: 
    bytes_stdin, bytes_stdout = stdin, stdout 

def main(): 
    for line in bytes_stdin: 
     try: 
      fields = line.split(b'"', 6) 
      bytes_stdout.write(fields[5] + b'\n') 
     except IndexError: 
      pass 

if __name__ == '__main__': 
    main() 

Sie werden jetzt stdout.write() als print() beharrt auf die stdoutTextIOBase Implementierung auf das Schreiben zu verwenden.

Beachten Sie, dass die .split() verwendet nun ein Bytes wörtliche b'"' und wir schreiben ein Byte-wörtliche b'\n' als auch (was normalerweise betreut von print() genommen werden).

Das obige ist kompatibel mit Python 2.6 und höher. Python 2.5 unterstützt das Präfix b nicht.