Frage

Ich möchte die Leistung eines Python-Skript verbessern und wurden mit cProfile einen Leistungsbericht zu generieren:

python -m cProfile -o chrX.prof ./bgchr.py ...args...

Ich öffnete diese chrX.prof Datei mit Python pstats und ausgedruckte Statistik:

Python 2.7 (r27:82500, Oct  5 2010, 00:24:22) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pstats
>>> p = pstats.Stats('chrX.prof')
>>> p.sort_stats('name')
>>> p.print_stats()                                                                                                                                                                                                                        
Sun Oct 10 00:37:30 2010    chrX.prof                                                                                                                                                                                                      

         8760583 function calls in 13.780 CPU seconds                                                                                                                                                                                      

   Ordered by: function name                                                                                                                                                                                                               

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)                                                                                                                                                                    
        1    0.000    0.000    0.000    0.000 {_locale.setlocale}                                                                                                                                                                          
        1    1.128    1.128    1.128    1.128 {bz2.decompress}                                                                                                                                                                             
        1    0.002    0.002   13.780   13.780 {execfile}                                                                                                                                                                                   
  1750678    0.300    0.000    0.300    0.000 {len}                                                                                                                                                                                        
       48    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}                                                                                                                                                          
        1    0.000    0.000    0.000    0.000 {method 'close' of 'file' objects}                                                                                                                                                           
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}                                                                                                                                             
  1750676    0.496    0.000    0.496    0.000 {method 'join' of 'str' objects}                                                                                                                                                             
        1    0.007    0.007    0.007    0.007 {method 'read' of 'file' objects}                                                                                                                                                            
        1    0.000    0.000    0.000    0.000 {method 'readlines' of 'file' objects}                                                                                                                                                       
        1    0.034    0.034    0.034    0.034 {method 'rstrip' of 'str' objects}                                                                                                                                                           
       23    0.000    0.000    0.000    0.000 {method 'seek' of 'file' objects}                                                                                                                                                            
  1757785    1.230    0.000    1.230    0.000 {method 'split' of 'str' objects}                                                                                                                                                            
        1    0.000    0.000    0.000    0.000 {method 'startswith' of 'str' objects}                                                                                                                                                       
  1750676    0.872    0.000    0.872    0.000 {method 'write' of 'file' objects}                                                                                                                                                           
        1    0.007    0.007   13.778   13.778 ./bgchr:3(<module>)                                                                                                                                                                          
        1    0.000    0.000   13.780   13.780 <string>:1(<module>)                                                                                                                                                                         
        1    0.001    0.001    0.001    0.001 {open}                                                                                                                                                                                       
        1    0.000    0.000    0.000    0.000 {sys.exit}                                                                                                                                                                                   
        1    0.000    0.000    0.000    0.000 ./bgchr:36(checkCommandLineInputs)                                                                                                                                                           
        1    0.000    0.000    0.000    0.000 ./bgchr:27(checkInstallation)                                                                                                                                                                
        1    1.131    1.131   13.701   13.701 ./bgchr:97(extractData)                                                                                                                                                                      
        1    0.003    0.003    0.007    0.007 ./bgchr:55(extractMetadata)                                                                                                                                                                  
        1    0.064    0.064   13.771   13.771 ./bgchr:5(main)                                                                                                                                                                              
  1750677    8.504    0.000   11.196    0.000 ./bgchr:122(parseJarchLine)                                                                                                                                                                  
        1    0.000    0.000    0.000    0.000 ./bgchr:72(parseMetadata)                                                                                                                                                                    
        1    0.000    0.000    0.000    0.000 /home/areynolds/proj/tools/lib/python2.7/locale.py:517(setlocale) 

Frage: Was kann ich über join, split und write Operationen die offensichtliche Auswirkungen reduzieren sie auf die Leistung dieses Skript

Wenn es relevant ist, ist hier die vollständige Quellcode für das Skript in Frage:

#!/usr/bin/env python

import sys, os, time, bz2, locale

def main(*args):
    # Constants
    global metadataRequiredFileSize
    metadataRequiredFileSize = 8192
    requiredVersion = (2,5)

    # Prep
    global whichChromosome
    whichChromosome = "all"
    checkInstallation(requiredVersion)
    checkCommandLineInputs()
    extractMetadata()
    parseMetadata()
    if whichChromosome == "--list":
        listMetadata()
        sys.exit(0)

    # Extract
    extractData()   
    return 0

def checkInstallation(rv):
    currentVersion = sys.version_info
    if currentVersion[0] == rv[0] and currentVersion[1] >= rv[1]:
        pass
    else:
        sys.stderr.write( "\n\t[%s] - Error: Your Python interpreter must be %d.%d or greater (within major version %d)\n" % (sys.argv[0], rv[0], rv[1], rv[0]) )
        sys.exit(-1)
    return

def checkCommandLineInputs():
    cmdName = sys.argv[0]
    argvLength = len(sys.argv[1:])
    if (argvLength == 0) or (argvLength > 2):
        sys.stderr.write( "\n\t[%s] - Usage: %s [<chromosome> | --list] <bjarch-file>\n\n" % (cmdName, cmdName) )
        sys.exit(-1)
    else:   
        global inFile
        global whichChromosome
        if argvLength == 1:
            inFile = sys.argv[1]
        elif argvLength == 2:
            whichChromosome = sys.argv[1]
            inFile = sys.argv[2]
        if inFile == "-" or inFile == "--list":
            sys.stderr.write( "\n\t[%s] - Usage: %s [<chromosome> | --list] <bjarch-file>\n\n" % (cmdName, cmdName) )
            sys.exit(-1)
    return

def extractMetadata():
    global metadataList
    global dataHandle
    metadataList = []
    dataHandle = open(inFile, 'rb')
    try:
        for data in dataHandle.readlines(metadataRequiredFileSize):     
            metadataLine = data
            metadataLines = metadataLine.split('\n')
            for line in metadataLines:      
                if line:
                    metadataList.append(line)
    except IOError:
        sys.stderr.write( "\n\t[%s] - Error: Could not extract metadata from %s\n\n" % (sys.argv[0], inFile) )
        sys.exit(-1)
    return

def parseMetadata():
    global metadataList
    global metadata
    metadata = []
    if not metadataList: # equivalent to "if len(metadataList) > 0"
        sys.stderr.write( "\n\t[%s] - Error: No metadata in %s\n\n" % (sys.argv[0], inFile) )
        sys.exit(-1)
    for entryText in metadataList:
        if entryText: # equivalent to "if len(entryText) > 0"
            entry = entryText.split('\t')
            filename = entry[0]
            chromosome = entry[0].split('.')[0]
            size = entry[1]
            entryDict = { 'chromosome':chromosome, 'filename':filename, 'size':size }
            metadata.append(entryDict)
    return

def listMetadata():
    for index in metadata:
        chromosome = index['chromosome']
        filename = index['filename']
        size = long(index['size'])
        sys.stdout.write( "%s\t%s\t%ld" % (chromosome, filename, size) )
    return

def extractData():
    global dataHandle
    global pLength
    global lastEnd
    locale.setlocale(locale.LC_ALL, 'POSIX')
    dataHandle.seek(metadataRequiredFileSize, 0) # move cursor past metadata
    for index in metadata:
        chromosome = index['chromosome']
        size = long(index['size'])
        pLength = 0L
        lastEnd = ""
        if whichChromosome == "all" or whichChromosome == index['chromosome']:
            dataStream = dataHandle.read(size)
            uncompressedData = bz2.decompress(dataStream)
            lines = uncompressedData.rstrip().split('\n')
            for line in lines:
                parseJarchLine(chromosome, line)
            if whichChromosome == chromosome:
                break
        else:
            dataHandle.seek(size, 1) # move cursor past chromosome chunk

    dataHandle.close()
    return

def parseJarchLine(chromosome, line):
    global pLength
    global lastEnd
    elements = line.split('\t')
    if len(elements) > 1:
        if lastEnd:
            start = long(lastEnd) + long(elements[0])
            lastEnd = long(start + pLength)
            sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))
        else:
            lastEnd = long(elements[0]) + long(pLength)
            sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, long(elements[0]), lastEnd, '\t'.join(elements[1:])))
    else:
        if elements[0].startswith('p'):
            pLength = long(elements[0][1:])
        else:
            start = long(long(lastEnd) + long(elements[0]))
            lastEnd = long(start + pLength)
            sys.stdout.write("%s\t%ld\t%ld\n" % (chromosome, start, lastEnd))               
    return

if __name__ == '__main__':
    sys.exit(main(*sys.argv))

Bearbeiten

Wenn ich die sys.stdout.write Anweisung in dem ersten bedingten von parseJarchLine() kommentieren, dann meiner Laufzeit geht von 10,2 sec bis 4,8 sec:

# with first conditional's "sys.stdout.write" enabled
$ time ./bgchr chrX test.bjarch > /dev/null
real    0m10.186s                                                                                                                                                                                        
user    0m9.917s                                                                                                                                                                                         
sys 0m0.160s  

# after first conditional's "sys.stdout.write" is commented out                                                                                                                                                                                           
$ time ./bgchr chrX test.bjarch > /dev/null
real    0m4.808s                                                                                                                                                                                         
user    0m4.561s                                                                                                                                                                                         
sys 0m0.156s

Ist Schreiben wirklich stdout so teuer in Python?

War es hilfreich?

Lösung

ncalls ist nur relevant, in dem Maße, dass die Zahlen gegen andere zählt wie die Anzahl der Zeichen / Felder / Zeilen in einer Datei zu vergleichen können Anomalien Highligh; tottime und cumtime ist das, was wirklich zählt. cumtime ist die Zeit, in der Funktion / Methode verbrachte mit die verbrachte Zeit in den Funktionen / Methoden, die es nennt; tottime ist die Zeit, in der Funktion / Methode ausgegeben ohne die verbrachte Zeit in den Funktionen / Methoden, die es nennt.

Ich finde es hilfreich, die Statistiken über tottime zu sortieren und wieder auf cumtime, nicht auf name.

bgchar auf jeden Fall bezieht sich auf die Ausführung des Skripts und ist nicht unerheblich, da es 8,9 Sekunden aus 13,5 aufgreift; dass 8,9 Sekunden Zeit in den Funktionen / Methoden beinhalten NICHT, dass es nennt! Lesen Sie sorgfältig, was @Lie Ryan sagt über Ihr Skript in Funktionen Modularisierung und seine Ratschläge umzusetzen. Ebenso was @jonesy sagt.

string wird erwähnt, weil Sie import string und verwenden Sie es in nur einem Ort: string.find(elements[0], 'p'). Bei einer anderen Zeile in der Ausgabe werden Sie feststellen, dass string.find nur einmal aufgerufen wurde, so dass es in diesem Laufe dieses Skripts keine Performance-Problem ist. ABER: Sie verwenden überall sonst str Methoden. string Funktionen werden heute als veraltet und werden durch Aufruf der entsprechenden str Methode implementiert. Sie wäre besser elements[0].find('p') == 0 für eine exakte, aber schneller äquivalent zu schreiben, und könnte zu verwenden elements[0].startswith('p') mag die Leser sparen würde fragen, ob das == 0 tatsächlich == -1 sein sollte.

Die vier genannten Methoden von @Bernd Peter nehmen nur 3,7 Sekunden bei einer Gesamtausführungszeit von 13,541 Sekunden. Vor zu viel über diejenigen Sorgen, modularisieren Ihr Skript in Funktionen, laufen cProfile wieder, und sortieren die Statistiken von tottime.

Update nach Frage mit geändertenen Skript überarbeitet:

„“ „Frage: Was kann ich tun kommen über, Split und Schreiboperationen die offensichtlichen Auswirkungen sie auf der Leistung dieses Skripts zu reduzieren“ "

Hä? Diejenigen 3 nehmen zusammen 2,6 Sekunden aus dem insgesamt 13,8. Ihre parseJarchLine Funktion nimmt 8,5 Sekunden (die Zeit nicht durch Funktionen / Methoden genommen nicht enthalten, dass es nennt. assert(8.5 > 2.6)

Bernd hat bereits darauf Sie an, was Sie sich anschauen sollten mit denen zu tun. Sie spalten sich unnötig die Linie vollständig nur um es wieder zu kommen, wenn es auszuschreiben. Sie müssen nur das erste Element prüfen. Statt elements = line.split('\t') tun elements = line.split('\t', 1) und ersetzen '\t'.join(elements[1:]) durch elements[1].

Nun des Tauchgangs in den Körper parseJarchLine lassen. Die Anzahl der Verwendungen in der Quelle und Weise der Nutzung der long eingebaute Funktion erstaunlich sind. Auch erstaunlicher ist die Tatsache, dass long nicht im cProfile Ausgabe erwähnt.

Warum brauchen Sie überhaupt long? Dateien über 2 GB? OK, dann müssen Sie bedenken, dass seit Python 2.2, int Überlauf Promotion long verursacht stattdessen eine Ausnahme zu heben. Sie können die Vorteile der schnelleren Ausführung von arithmetischen int nehmen. Sie müssen auch bedenken, dass long(x) tun, wenn x ist bereits nachweislich ein long ist eine Verschwendung von Ressourcen.

Hier ist die parseJarchLine Funktion mit dem Entfernen von Abfall deutlichen Veränderungen [1] und Wechsel-to-int ändert gekennzeichnet [2]. Gute Idee: Bearbeite in kleinen Schritten, Re-Test, Re-Profil

.
def parseJarchLine(chromosome, line):
    global pLength
    global lastEnd
    elements = line.split('\t')
    if len(elements) > 1:
        if lastEnd != "":
            start = long(lastEnd) + long(elements[0])
            # [1] start = lastEnd + long(elements[0])
            # [2] start = lastEnd + int(elements[0])
            lastEnd = long(start + pLength)
            # [1] lastEnd = start + pLength
            sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))
        else:
            lastEnd = long(elements[0]) + long(pLength)
            # [1] lastEnd = long(elements[0]) + pLength
            # [2] lastEnd = int(elements[0]) + pLength
            sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, long(elements[0]), lastEnd, '\t'.join(elements[1:])))
    else:
        if elements[0].startswith('p'):
            pLength = long(elements[0][1:])
            # [2] pLength = int(elements[0][1:])
        else:
            start = long(long(lastEnd) + long(elements[0]))
            # [1] start = lastEnd + long(elements[0])
            # [2] start = lastEnd + int(elements[0])
            lastEnd = long(start + pLength)
            # [1] lastEnd = start + pLength
            sys.stdout.write("%s\t%ld\t%ld\n" % (chromosome, start, lastEnd))               
    return

Update nach Frage zu sys.stdout.write

Wenn die Aussage, dass Sie kommentierte, war so etwas wie das Original:

sys.stdout.write("%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:])))

Dann ist Ihre Frage ... interessant. Versuchenfolgt aus:

payload = "%s\t%ld\t%ld\t%s\n" % (chromosome, start, lastEnd, '\t'.join(elements[1:]))
sys.stdout.write(payload)

Jetzt Kommentar aus der sys.stdout.write Aussage ...

Durch die Art und Weise, jemand in einem Kommentar erwähnt über das Einbrechen in mehr als ein Schreib ... haben Sie dies in Betracht gezogen? Wie viele Bytes im Durchschnitt in den Elementen [1:]? In Chromosom?

=== Themenwechsel: Es macht mir Sorgen, dass Sie lastEnd zu "" initialisieren, anstatt auf Null, und dass niemand hat es kommentiert. Jede Art und Weise, sollten Sie dieses Problem beheben, die eine ziemlich drastische Vereinfachung ermöglicht sowie das Hinzufügen in anderen Vorschlägen:

def parseJarchLine(chromosome, line):
    global pLength
    global lastEnd
    elements = line.split('\t', 1)
    if elements[0][0] == 'p':
        pLength = int(elements[0][1:])
        return
    start = lastEnd + int(elements[0])
    lastEnd = start + pLength
    sys.stdout.write("%s\t%ld\t%ld" % (chromosome, start, lastEnd))
    if elements[1:]:
        sys.stdout.write(elements[1])
    sys.stdout.write(\n)

Jetzt bin ich besorgt in ähnlicher Weise über die beiden globalen Variablen lastEnd und pLength - die parseJarchLine Funktion ist jetzt so klein, dass es wieder in den Körper ihres einzigen Anrufer, extractData gefaltet werden kann, die zwei globalen Variablen speichert, und eine gazillion Funktion aufruft. Sie könnten auch speichern Sie eine Unmenge Lookups von sys.stdout.write von write = sys.stdout.write einmal auf der Vorderseite extractData setzen und die Verwendung dieser statt.

BTW, die Skript-Tests für Python 2.5 oder besser; Haben Sie versucht, Profilieren auf 2.5 und 2.6?

Andere Tipps

Diese Ausgabe wird nützlicher sein, wenn Ihr Code modular aufgebaut ist, wie Lie Ryan erklärt hat. Doch ein paar Dinge, die Sie aus der Ausgabe abholen und nur auf dem Quellcode suchen:

Sie tun eine Menge Vergleiche, die nicht wirklich notwendig, in Python sind. Zum Beispiel statt:

if len(entryText) > 0:

Sie können einfach schreiben:

if entryText:

Eine leere Liste auswertet auf False in Python. Gleiches gilt für eine leere Zeichenfolge, die Sie auch Test für in Ihrem Code und das Ändern es auch der Code eine kürzere Bit machen würde und besser lesbar, so dass anstelle dieses:

   for line in metadataLines:      
        if line == '':
            break
        else:
            metadataList.append(line)

Sie können einfach tun:

for line in metadataLines:
    if line:
       metadataList.append(line)

Es gibt einige andere Probleme mit diesem Code sowohl in Bezug auf Organisation und Leistung. Sie ordnen Variablen mehrmals auf die gleiche Sache, anstatt nur einmal eine Objektinstanz zu schaffen und alle Zugriffe auf das Objekt zu tun, zum Beispiel. Dadurch würde die Anzahl der Aufgaben, und auch die Anzahl der globalen Variablen reduzieren. Ich will nicht übermäßig kritisch klingen, aber dieser Code scheint nicht mit Leistung im Verstand geschrieben werden.

Die Einträge relevant für mögliche Optimierung sind solche mit hohen Werten für ncalls und tottime . bgchr:4(<module>) und <string>:1(<module>) wahrscheinlich auf die Ausführung Ihres Modulkörper beziehen und sind hier nicht relevant.

Offensichtlich Ihr Leistungsproblem kommt aus String-Verarbeitung. Dies sollte vielleicht reduziert werden. Die Hot Spots sind split, join und sys.stdout.write. bz2.decompress scheint auch teuer zu sein.

Ich schlage vor, Sie versuchen, die folgenden:

  • Ihre Hauptdaten scheint von Tabulatoren getrennte CSV-Werte zu bestehen. Testen Sie, wenn CSV-Leser eine bessere Leistung.
  • sys.stdout wird Zeile gepuffert und gespült jedes Mal, wenn ein Newline geschrieben. Betrachten Sie das Schreiben in eine Datei mit einer größeren Puffergröße.
  • Anstelle von Verbindungselementen, bevor sie sie schreibt, schreibt sie nacheinander in die Ausgabedatei. Sie können sich auch CSV-Writer.
  • Anstatt die Daten sofort in einem einzigen String Dekomprimieren, verwenden Sie ein BZ2File Objekt und übergeben, daß in der CSV-Leser.

Es scheint, dass der Schleifenkörper, die Daten tatsächlich dekomprimiert nur einmal aufgerufen wird. Vielleicht finden Sie einen Weg, um den Anruf dataHandle.read(size) zu vermeiden, die eine große Zeichenfolge erzeugt, die dann dekomprimiert wird, und die Arbeit mit dem Dateiobjekt direkt an.

Nachtrag: BZ2File ist wahrscheinlich in Ihrem Fall nicht anwendbar, weil es einen Dateinamen als Argument benötigt. Was Sie brauchen, ist so etwas wie ein Dateiobjekt Ansicht mit integrierten Lesegrenze, vergleichbar mit ZipExtFile aber mit BZ2Decompressor für die Dekompression.

Meine hier Hauptsache ist, dass der Code geändert werden soll, um eine iterative Verarbeitung Ihrer Daten durchzuführen, statt sie in als Ganze von Schlürfen und Spaltung wieder danach.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top