Domanda

Vorrei migliorare le prestazioni di uno script Python e sono state usando cProfile per generare un report di prestazioni:

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

Ho aperto questo file chrX.prof con pstats di Python e stampato le statistiche:

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) 

Domanda: Cosa posso fare per operazioni join, split e write per ridurre l'impatto apparente che hanno sulle prestazioni di questo script

Se è rilevante, ecco il codice sorgente completo per lo script in questione:

#!/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))

Modifica

Se io commento le istruzioni sys.stdout.write nel primo condizionale di parseJarchLine(), poi il mio tempo di esecuzione va dal 10,2 sec a 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

sta scrivendo stdout davvero così costoso in Python?

È stato utile?

Soluzione

ncalls è rilevante solo nella misura in cui si confrontano i numeri contro altri conta come il numero di caratteri / campi / righe in un file può highligh anomalie; tottime e cumtime è ciò che conta davvero. cumtime è il tempo trascorso nel / metodo funzione compreso il tempo trascorso in funzioni / metodi che chiama; tottime è il tempo trascorso nella funzione / metodo di escluso il tempo trascorso nelle funzioni / metodi che chiama.

Trovo utile per ordinare le statistiche su tottime e di nuovo su cumtime, non su name.

bgchar sicuramente si riferisce all'esecuzione dello script e non è irrilevante come esso prende 8,9 secondi su 13.5; che 8,9 secondi non include il tempo nelle funzioni / metodi che chiama! Leggi con attenzione quello che dice @Lie Ryan sulla modularizzazione lo script in funzioni, e mettere in atto il suo consiglio. Allo stesso modo ciò che @jonesy dice.

string è menzionato perché si import string e utilizzarlo in un solo posto: string.find(elements[0], 'p'). Su un'altra linea nell'output si noterà che string.find è stato chiamato solo una volta, quindi non è un problema di prestazioni in questo percorso di questo script. TUTTAVIA: si utilizzano i metodi str ovunque. funzioni string sono obsoleti e oggi sono implementati chiamando il metodo str corrispondente. Si sarebbe meglio scrivere elements[0].find('p') == 0 per un esatto equivalente, ma più veloce, e avrebbe fatto piacere l'uso elements[0].startswith('p') che permetterebbe di risparmiare i lettori chiedendo se che == 0 dovrebbe essere effettivamente == -1.

I quattro metodi menzionati da @Bernd Petersohn occupano solo 3,7 secondi su un tempo di esecuzione totale di 13.541 secondi. Prima di preoccuparsi troppo quelli, modularizzare lo script in funzioni, eseguire nuovamente Cprofile, e ordinare le statistiche di tottime.

Aggiornamento su domande rivisto con lo script modificato:

"" "Domanda: Cosa posso fare per unire, dividere le operazioni di scrittura e per ridurre l'impatto apparente che hanno sulle prestazioni di questo script" "

Eh? Coloro 3 complessivamente in 2,6 secondi su un totale di 13,8. La funzione parseJarchLine sta prendendo 8,5 secondi (che non comprende tempo impiegato da funzioni / metodi che chiama. assert(8.5 > 2.6)

Bernd ha già puntato a quello che si potrebbe considerare di fare con quelli. Si sono inutilmente dividendo la linea completamente unica per unire di nuovo durante la scrittura fuori. È necessario esaminare solo il primo elemento. Invece di fare elements = line.split('\t') elements = line.split('\t', 1) e sostituire '\t'.join(elements[1:]) da elements[1].

Ora diamo tuffo nel corpo di parseJarchLine. Il numero di impieghi nella sorgente e maniera degli usi della long funzione incorporata sono sorprendenti. Anche sorprendente è il fatto che non è menzionato long nell'output Cprofile.

Perché avete bisogno di long a tutti? File di oltre 2 GB? OK, allora è necessario considerare che dal Python 2.2, int overflow causa la promozione a long invece di sollevare un'eccezione. È possibile usufruire di esecuzione più veloce di int aritmetica. È inoltre necessario considerare che facendo long(x) quando x è già palesemente un long è uno spreco di risorse.

Questa è la funzione parseJarchLine con la rimozione dei rifiuti variazione marcata [1] e cambiando-to-int notevoli cambiamenti [2]. Buona idea: le modifiche apportate in piccoli passi, re-test, ri-profilo

.
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

Aggiorna dopo domanda su sys.stdout.write

Se la dichiarazione che si ha commentato fuori era qualcosa di simile a quello originale:

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

Quindi la tua domanda è ... interessante. Provarein questo modo:

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

Ora come commento le istruzioni sys.stdout.write ...

A proposito, qualcuno ha menzionato in un commento di rompere questo in più di una scrittura ... avete considerato questo? Il numero di byte, in media, negli elementi [1:]? In cromosoma?

=== cambio di argomento: Mi preoccupa che si inizializza lastEnd a "" piuttosto che a zero, e che nessuno ha commentato su di esso. In qualsiasi modo, si dovrebbe risolvere questo problema, che permette una semplificazione piuttosto drastica, più l'aggiunta di suggestioni degli altri:

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)

Ora sto allo stesso preoccupato per i due variabili globali lastEnd e pLength - la funzione parseJarchLine ora è così piccolo che può essere ripiegato nel corpo del suo chiamante suola, extractData, che consente di risparmiare due variabili globali, e un funzione gazillion chiama. Si potrebbe anche risparmiare un gazillion le ricerche di sys.stdout.write mettendo write = sys.stdout.write una volta la parte anteriore del extractData e l'utilizzo che, invece.

A proposito, i test di script per Python 2.5 o superiore; Hai provato profiling su 2.5 e 2.6?

Altri suggerimenti

Questa uscita sarà più utile se il codice è più modulare come Lie Ryan ha dichiarato. Tuttavia, un paio di cose che si può prendere dall'uscita e solo guardando il codice sorgente:

Si sta facendo un sacco di paragoni che non sono effettivamente necessarie in Python. Ad esempio, invece di:

if len(entryText) > 0:

Si può solo scrivere:

if entryText:

Un elenco Esamina vuoti a False in Python. Lo stesso vale per una stringa vuota, che anche di test per nel codice, e la modifica sarebbe anche rendere il codice un po 'più breve e più leggibile, così invece di questo:

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

Si può semplicemente fare:

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

Ci sono diversi altri problemi con questo codice sia in termini di organizzazione e prestazioni. È possibile assegnare le variabili più volte per la stessa cosa invece di creare un'istanza di un oggetto, una volta e facendo tutti gli accessi per l'oggetto, per esempio. In questo modo si ridurrebbe il numero di incarichi, e anche il numero di variabili globali. Io non voglio sembrare eccessivamente critico, ma questo codice non sembra essere scritto con prestazioni in mente.

Le voci rilevanti per un eventuale ottimizzazione sono quelli con alti valori di ncalls e tottime . bgchr:4(<module>) e <string>:1(<module>) probabilmente si riferiscono alla esecuzione del corpo del modulo e non sono rilevanti qui.

Ovviamente, il problema delle prestazioni viene dalla elaborazione delle stringhe. Questo dovrebbe forse essere ridotto. I punti caldi sono split, join e sys.stdout.write. bz2.decompress sembra anche essere costoso.

Vi suggerisco di provare il seguente:

  • I dati principale sembra consistere di scheda separata valori CSV. Provate, se esegue lettore CSV migliori.
  • sys.stdout è la linea tamponato e lavato ogni volta che una nuova riga viene scritta. Prendere in considerazione la scrittura su un file con un buffer di dimensioni maggiori.
  • Invece di unire elementi prima di scrivere fuori, li scrivere in sequenza nel file di output. Si può anche considerare l'utilizzo di scrittore CSV.
  • Invece di decompressione dei dati contemporaneamente in una singola stringa, utilizzare un oggetto BZ2File e passare che al lettore CSV.

Sembra che il corpo del ciclo che decomprime i dati in realtà viene richiamato solo una volta. Forse si trova un modo per evitare il dataHandle.read(size) chiamata, che produce una stringa enorme che viene poi decompresso, e di lavorare con il file oggetto direttamente.

Addendum: BZ2File probabilmente non è applicabile nel tuo caso, perché richiede un argomento filename. Quello che vi serve è qualcosa di simile a una vista oggetto file con limite di lettura integrata, paragonabile a ZipExtFile ma utilizzando BZ2Decompressor per la decompressione.

Il mio punto principale è che il codice dovrebbe essere cambiato per effettuare un'elaborazione più iterativo di dati, invece di slurping in nel suo insieme e la divisione di nuovo in seguito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top