Question

Je voudrais améliorer les performances d'un script Python et ont été à l'aide cProfile pour générer un rapport de performance:

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

J'ai ouvert ce fichier chrX.prof avec pstats de Python et d'imprimer les statistiques:

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) 

Question: Que puis-je faire des opérations join, split et write pour réduire l'impact apparent qu'ils ont sur les performances de ce script

Si elle est pertinente, voici le code source complet du script en question:

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

EDIT

Si je commente la déclaration de sys.stdout.write dans la première condition de parseJarchLine(), puis mon exécution passe de 10,2 sec à 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

est en train d'écrire à stdout vraiment si cher que ça en Python?

Était-ce utile?

La solution

ncalls est pertinent que dans la mesure où l'on compare les numéros contre d'autres chiffres tels que le nombre de caractères / champs / lignes dans un fichier peut highligh anomalies; tottime et cumtime est ce qui importe vraiment. cumtime est le temps passé dans la fonction / méthode dont le temps passé dans les fonctions / méthodes qu'il appelle; tottime est le temps passé dans la fonction / méthode sauf le temps passé dans les fonctions / méthodes qu'il appelle.

Je trouve utile de trier les statistiques sur tottime et encore sur cumtime, pas name.

bgchar définitivement fait référence à l'exécution du script et n'est pas inutile car il faut 8,9 secondes sur 13,5; que 8,9 secondes ne contient pas de temps dans les fonctions / méthodes qu'il appelle! Lisez attentivement ce @Lie Ryan dit à propos modularisation votre script en fonctions, et mettre en œuvre ses conseils. De même ce que dit @jonesy.

string est mentionné parce que vous import string et l'utiliser en un seul endroit: string.find(elements[0], 'p'). Sur une autre ligne dans la sortie, vous remarquerez que string.find a été appelé une seule fois, il est donc pas un problème de performance dans cette série de ce script. CEPENDANT: Vous utilisez des méthodes de str partout ailleurs. fonctions string sont dépréciées de nos jours et sont mises en œuvre en appelant la méthode de str correspondante. Vous seriez mieux écrire elements[0].find('p') == 0 pour une exacte mais plus rapide équivalent, et peut-être que l'utilisation elements[0].startswith('p') qui sauverait les lecteurs se demandant si cette == 0 devrait effectivement être == -1.

Les quatre méthodes mentionnées par @Bernd Petersohn prennent seulement 3,7 secondes d'un temps d'exécution total de 13.541 secondes. Avant de se préoccuper trop de ceux-ci, modularisation votre script en fonctions, exécutez cprofile à nouveau, et trier les statistiques par tottime.

Mise à jour après la question révisée avec le script modifié:

« » « Question: Que puis-je joindre à propos, les opérations de division et d'écriture pour réduire l'impact apparent qu'ils ont sur les performances de ce script » "

Huh? Les 3 prennent ensemble 2,6 secondes sur le total de 13,8. Votre fonction parseJarchLine prend 8,5 secondes (qui ne comprend pas le temps pris par des fonctions / méthodes qu'il appelle. assert(8.5 > 2.6)

Bernd vous a déjà fait ce que vous pourriez envisager de faire avec ceux-ci. Vous fractionnez inutilement la ligne complètement seulement pour le joindre à nouveau lors de l'écriture dehors. Vous devez vérifier que le premier élément. Au lieu de faire elements = line.split('\t') elements = line.split('\t', 1) et le remplacer par '\t'.join(elements[1:]) elements[1].

Maintenant, nous allons plonger dans le corps de parseJarchLine. Le nombre d'utilisations de la source et la manière des utilisations du long fonction intégrée sont étonnantes. Aussi étonnant est le fait que long ne figure pas dans la sortie cprofile.

Pourquoi avez-vous besoin long du tout? Les fichiers de plus de 2 Go? OK, alors vous devez considérer que depuis Python 2.2, débordement de int provoque la promotion de long au lieu de soulever une exception. Vous pouvez profiter de l'exécution plus rapide de l'arithmétique int. Vous devez également considérer que faire long(x) lorsque x est déjà manifestement un long est un gaspillage de ressources.

Voici la fonction parseJarchLine avec l'élimination des déchets des changements marqués [1] et en changeant à des changements marqués int [2]. Bonne idée: apporter des changements dans les petites étapes, re-test, reprofiler

.
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

Mise à jour après question sur sys.stdout.write

Si la déclaration que vous avez dit sur était quelque chose comme l'original:

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

Alors, votre question est ... intéressante. Essayerceci:

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

maintenant commentaire sur la déclaration de sys.stdout.write ...

Par ailleurs, quelqu'un a mentionné dans un commentaire au sujet de briser ce dans plus d'une écriture ... avez-vous pensé cela? Combien d'octets en moyenne dans les éléments [1:]? Dans le chromosome?

=== changement de sujet: Il me inquiète que vous initialisez lastEnd à "" plutôt que de zéro, et que personne n'a commenté à ce sujet. De toute façon, vous devez résoudre ce problème, ce qui permet une simplification assez radicale, plus l'ajout dans les suggestions des autres:

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)

Maintenant, je suis inquiet de la même sur les deux variables globales et lastEnd pLength - la fonction parseJarchLine est si petit qu'il peut être replié dans le corps de son unique interlocuteur, extractData, qui enregistre deux variables globales, et appelle la fonction gazillion. Vous pourriez également enregistrer un gazillion de recherches sys.stdout.write en mettant write = sys.stdout.write une fois l'avant de extractData et en utilisant cette place.

BTW, les tests de script pour Python 2.5 ou mieux; Avez-vous essayé de profilage sur 2.5 et 2.6?

Autres conseils

Cette sortie va être plus utile si votre code est plus modulaire Lie Ryan a déclaré. Cependant, un certain nombre de choses que vous pouvez chercher à la sortie et juste regarder le code source:

Vous êtes en train de faire beaucoup de comparaisons qui ne sont pas réellement nécessaires en Python. Par exemple, au lieu de:

if len(entryText) > 0:

Vous pouvez simplement écrire:

if entryText:

Une liste vide est évaluée à faux en Python. La même chose est vrai pour une chaîne vide, que vous avez test dans votre code, et le changer serait également le code un peu plus court et plus lisible, donc au lieu de ceci:

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

Vous pouvez simplement faire:

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

Il y a plusieurs autres problèmes avec ce code en termes de l'organisation et de la performance. Vous attribuez des variables plusieurs fois à la même chose au lieu de simplement créer une instance d'objet une fois et de faire tous les accès sur l'objet, par exemple. Faire cela réduirait le nombre de missions, ainsi que le nombre de variables globales. Je ne veux pas paraître trop critique, mais ce code ne semble pas être écrit avec des performances à l'esprit.

Les entrées pertinentes pour l'optimisation possibles sont ceux avec des valeurs élevées pour ncalls et tottime . bgchr:4(<module>) et <string>:1(<module>) font probablement référence à l'exécution de votre corps de module et ne sont pas pertinentes en l'espèce.

De toute évidence, votre problème de performance provient de traitement de chaîne. Cela devrait peut-être réduit. Les points chauds sont split, join et sys.stdout.write. bz2.decompress semble aussi être coûteux.

Je vous suggère d'essayer ce qui suit:

  • Vos données principale semble consister en valeurs séparées par des tabulations CSV. Essayez, si le lecteur CSV fonctionne mieux.
  • sys.stdout est en ligne et tamponne rincée à chaque fois qu'un saut de ligne est écrite. Pensez à écrire dans un fichier avec une plus grande mémoire tampon.
  • Au lieu de se joindre à des éléments avant de les écrire sur, les écrire de manière séquentielle dans le fichier de sortie. Vous pouvez également envisager d'utiliser l'écrivain CSV.
  • Au lieu de décompresser les données à la fois en une seule chaîne, utilisez un objet BZ2File et de transmettre au lecteur que CSV.

Il semble que le corps de boucle qui décompresse les données ne fait invoqué qu'une seule fois. Peut-être vous trouver un moyen d'éviter l'dataHandle.read(size) d'appel, qui produit une grande chaîne qui est ensuite décompressé, et de travailler avec l'objet fichier directement.

Addendum: BZ2File est probablement pas applicable dans votre cas, car il nécessite un argument de nom de fichier. Qu'est-ce que vous avez besoin est quelque chose comme une vue de l'objet fichier avec limite de lecture intégrée, comparable à ZipExtFile mais en utilisant BZ2Decompressor pour la décompression.

Mon point principal est que votre code doit être modifié pour effectuer un traitement plus itérative de vos données au lieu de siphonage dans son ensemble et le fractionnement encore par la suite.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top