Qu'est-ce que ce cProfile résultat me dire que je dois résoudre ce problème?
Je voudrais améliorer les performances d'un script Python et ont été à l'aide de cProfile
pour générer un rapport sur le rendement:
python -m cProfile -o chrX.prof ./bgchr.py ...args...
J'ai ouvert ce chrX.prof
fichier avec Python pstats
et imprimé 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 join
, split
et write
opérations afin de réduire l'impact apparent sur l'exécution de ce script?
Si elle est pertinente, voici l'intégralité du code source 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))
MODIFIER
Si j'commentaire la sys.stdout.write
énoncé dans la première condamnation de parseJarchLine()
, puis mon moteur d'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 écrit à stdout
vraiment si cher que ça en Python?
Casser le code pour de petites réunions. Python cProfile est assez inutile pour le code qui est écrit comme une grande partie, parce que c'est une fonction au niveau du générateur de profils, pas ligne par ligne profiler. En attendant, vous pouvez gagner de l'augmentation de la vitesse si vous mettez tout dans une fonction main (), car en Python accéder à des variables globales est plus lent que l'accès à une variable locale.
Ryan: Regardez les chiffres! Ces sont suffisamment détaillés pour montrer où l'optimisation est nécessaire. L'accès aux variables globales n'est pas pertinente ici, et la fois pour bgchr:4(<module>) et <string>:1(<module>) correspondent à la somme des temps d'exécution.
Petersohn: Veuillez considérer la possibilité que vous vous trompez. Voir ma réponse.
J'ai remplacé le code source et résultant cProfile les résultats de l'analyse. Si vous avez un moment pour prendre un coup d'oeil et d'offrir des suggestions, je vous en serais reconnaissant. Merci.
Reynolds: Dans
Ryan: Regardez les chiffres! Ces sont suffisamment détaillés pour montrer où l'optimisation est nécessaire. L'accès aux variables globales n'est pas pertinente ici, et la fois pour bgchr:4(<module>) et <string>:1(<module>) correspondent à la somme des temps d'exécution.
Petersohn: Veuillez considérer la possibilité que vous vous trompez. Voir ma réponse.
J'ai remplacé le code source et résultant cProfile les résultats de l'analyse. Si vous avez un moment pour prendre un coup d'oeil et d'offrir des suggestions, je vous en serais reconnaissant. Merci.
Reynolds: Dans
parseJarchLine()
, il suffirait de diviser la ligne du premier onglet: elements = line.split('\t', 1)
. Ce serait faire la jointure suivante obsolète: remplacer l'expression '\t'.join(elements[1:]
par elements[1]
. Aussi, si cette deuxième partie peut devenir énorme, les performances peuvent encore être améliorées si vous n'avez pas l'intégrer dans la chaîne de format. Au lieu de cela, utiliser des appels à sys.stdout.write
à la sortie de la première partie, la deuxième partie et la finale de saut de ligne.OriginalL'auteur Alex Reynolds | 2010-10-09
Vous devez vous connecter pour publier un commentaire.
ncalls
est pertinente que dans la mesure où de comparer les chiffres à l'encontre d'autres chefs d'accusation tels que le nombre de caractères/champs/lignes dans un fichier peuvent hi anomalies;tottime
etcumtime
est ce qui compte vraiment.cumtime
est le temps passé dans la fonction/méthode y compris le temps passé dans les fonctions/méthodes qu'il appelle;tottime
est le temps passé dans la fonction/méthode à l'exclusion de le temps passé dans les fonctions/méthodes qu'il appelle.Je trouve utile de trier les stats sur
tottime
et de nouveau surcumtime
, pas surname
.bgchar
certainement se réfère à l'exécution du script et n'est pas pertinent car il ne prend 8,9 secondes de 13.5; que 8,9 secondes ne comprend PAS le temps dans les fonctions/méthodes qu'il appelle! Lisez attentivement ce que @Mensonge Ryan dit à propos de modularising votre script dans les fonctions de, et de mettre en œuvre ses conseils. De même que @jonesy dit.string
, parce que vousimport string
et de l'utiliser dans un seul endroit:string.find(elements[0], 'p')
. Sur une autre ligne dans la sortie, vous remarquerez que chaîne de caractères.trouver a été appelé qu'une seule fois, il n'est donc pas un problème de performance dans cette exécution de ce script. CEPENDANT: Vous utilisezstr
méthodes de partout ailleurs.string
fonctions sont obsolètes de nos jours et sont mis en œuvre par l'appel de la correspondantestr
méthode. Vous serait mieux écritelements[0].find('p') == 0
exacte mais plus rapide équivalent, et aimeriez l'utiliserelements[0].startswith('p')
qui permettrait de sauver les lecteurs se demander si== 0
devrait en fait être== -1
.Les quatre méthodes mentionnées par @Bernd Petersohn prendre seulement 3,7 secondes d'un temps d'exécution total de 13.541 secondes. Avant de trop se préoccuper de ceux qui, modularise votre script dans les fonctions de, exécutez cProfile de nouveau, et de trier les stats par
tottime
.Mise à jour après la question révisé avec le changement de script:
"""Question: Que puis-je faire au sujet de rejoindre, split et les opérations d'écriture afin de réduire l'impact apparent sur l'exécution de ce script?""
Hein? Ces 3 ensemble prendre 2,6 secondes sur un total de 13,8. Votre parseJarchLine fonction est prise de 8,5 secondes (ce qui n'inclut pas le temps pris par les fonctions/méthodes qu'il appelle.
assert(8.5 > 2.6)
Bernd l'a déjà souligné-vous à ce que vous pourriez envisager de faire avec ceux-ci. Vous êtes inutilement le fractionnement de la ligne complètement à joindre de nouveau lors de l'écriture. Vous devez examiner uniquement le premier élément. Au lieu de
elements = line.split('\t')
neelements = line.split('\t', 1)
et remplacer'\t'.join(elements[1:])
parelements[1]
.Maintenant, nous allons plonger dans le corps de parseJarchLine. Le nombre d'utilisations dans la source et la façon de les utilisations de l'
long
fonction intégrée sont étonnantes. Aussi étonnant est le fait quelong
n'est pas mentionné dans la cProfile de sortie.Pourquoi avez-vous besoin
long
à tous? Les fichiers de plus de 2 Go? OK, alors vous devez considérer que, depuis Python 2.2,int
débordement provoque la promotion delong
au lieu de lever une exception. Vous pouvez prendre avantage de la rapidité d'exécution deint
de l'arithmétique. Vous devez également considérer que celalong(x)
quandx
est déjà manifestement unlong
est un gaspillage de ressources.Ici est la parseJarchLine fonction à l'enlèvement de déchets des changements marqués [1] et l'évolution-à-int changements marqués [2]. Bonne idée: apporter des modifications à petits pas, re-tester, re-profil.
Mise à jour après la question sur
sys.stdout.write
Si l'instruction que vous avez commenté, était tout comme l'originale:
Votre question est ... intéressant. Essayez ceci:
Maintenant commentaire la
sys.stdout.write
déclaration ...En passant, quelqu'un a mentionné dans un commentaire à propos de la rupture de ce en plus d'une écriture ... avez-vous pensé à cela? Combien d'octets en moyenne en éléments[1:] ? Dans le chromosome?
=== changer de sujet: Ce qui m'inquiète que vous utilisez
lastEnd
à""
plutôt que de zéro, et que personne n'a commenté sur elle. De toute façon, vous devriez corriger ce problème, ce qui permet une plutôt une simplification drastique plus l'ajout dans les suggestions des autres:Maintenant, je suis même inquiet à propos de deux variables globales
lastEnd
etpLength
-- le parseJarchLine fonction est si petit qu'il peut être plié dans le corps de son seul appelant,extractData
, ce qui permet d'économiser deux variables globales, et une foule d'appels de fonction. Vous pouvez également enregistrer une foule de recherches desys.stdout.write
en mettantwrite = sys.stdout.write
une fois le front deextractData
et en l'utilisant à la place.BTW, le script des tests pour Python 2.5 ou mieux; avez-vous essayé de profilage sur 2.5 et 2.6?
J'ai remplacé le code source et résultant cProfile les résultats de l'analyse. Si vous avez un moment pour prendre un coup d'oeil et d'offrir des suggestions, je vous en serais reconnaissant. Merci.
Je vous remercie pour vos conseils utiles. Je ne suis pas un expert Python et aurait eu aucune idée que
int
types sont promus à lalong
automatiquement, basée sur le travail dans d'autres langues. Toutefois, les modifications suggérées semblent seulement à raser à 0.8 sec. Mon script est toujours de prendre environ deux fois aussi long qu'uncsh/awk
solution (qui ne permettra pas l'utilisation deseek
base à accès aléatoire, comme Python, et devrait être plus lente). Sauf si il y a d'autres Python-langage-des optimisations spécifiques et astuces (qui est toujours possible d'écrire sur la sortie standard) je pense que j'ai peut-être à chercher dans une C-base de solution à ce stade.J'ai commencé l'exécution de ce sous Python 2.5.2 et il était tout aussi lent, je suis allé à Python 2.7 pour essayer d'obtenir toutes les améliorations de la vitesse. J'ai écrit une C-base de l'équivalent de ce script depuis quelques jours, et le résultat est rapide comme l'éclair, en comparaison, bien qu'avec une certaine bzip difficultés, je suis encore au travail. J'ai peut-code postal, plus tard, à des fins de comparaison.
Reynolds: Avez-vous testé la dernière suggestion de John Machin? Je pense que l'un des principaux problèmes de votre script est qu'il maintient une importante inutilement de la mémoire. Dans
extractData()
, de lire d'abord la non compressé bloc de données pour un seul chromosome. Puis une deuxième chaîne de l'décompressé de données est créée. Il contient environ 1,75 million de lignes. Il est facile d'atteindre 1 GO ici. Ensuite, vous créez une liste de ceux de 1,75 millions de lignes. Il serait préférable d'encapsuler les données non compressées dans StringIO et itérer sur ses lignes. Vous devez également communiqué les données compressées au début à l'aide dedel
.OriginalL'auteur John Machin
Cette sortie va être plus utile si votre code est plus modulaire que Mensonge Ryan a déclaré. Cependant, un couple de choses que vous pouvez ramasser à partir de la sortie et juste en regardant 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 évalue à False dans Python. En est de même pour une chaîne vide, ce qui vous aussi tester dans votre code, et en changeant cela permettrait aussi de rendre le code un peu plus court et plus lisible, donc au lieu de ceci:
Il vous suffit de faire:
Il y a plusieurs autres problèmes avec ce code, en termes d'organisation et de performance. Vous pouvez attribuer des variables à plusieurs reprises pour la même chose au lieu de simplement la création d'une instance de l'objet une fois et de faire tous les accès sur l'objet, par exemple. En faisant cela permettrait de réduire le nombre d'affectations, et aussi le nombre de variables globales. Je ne veux pas paraître trop critique, mais ce code ne semble pas être écrits avec des performances à l'esprit.
Tout d'abord, je pense que ce qui est suggéré ici par diverses personnes sont généralement de bons conseils. Deuxièmement, je pense toujours que l'utilisation généralisée des globals va avoir un impact sur quelque chose qui traite une grande quantité de données-mettre les fonctions dans une classe, et qui va presque complètement à l'écart. Aussi, enveloppant les choses dans une classe, il est beaucoup plus facile d'expérimenter avec des solutions telles que le filetage et le multitraitement (j'en faveur de multitraitement sur le filetage pour ce cas, fwiw). Montrant l'exemple de l'entrée serait susceptible de vous obtenir plus de commentaires.
if line == '': break; else: metadataList.append(line)
n'est pas le même queif line: metadataList.append(line)
OriginalL'auteur jonesy
Les entrées pertinentes pour l'optimisation possible sont ceux avec des valeurs élevées pour ncalls et tottime.
bgchr:4(<module>)
et<string>:1(<module>)
probablement référence à l'exécution de votre module de corps et ne sont pas pertinentes ici.Évidemment, les performances de votre problème vient de la chaîne de traitement. Cela devrait peut-être être réduit. Les points chauds sont
split
,join
etsys.stdout.write
.bz2.decompress
semble aussi être coûteux.Je vous suggère d'essayer le suivant:
Il semble que le corps de la boucle que fait décompresse des données est invoqué qu'une seule fois. Peut-être vous trouver un moyen pour éviter l'appel
dataHandle.read(size)
, ce qui produit une énorme chaîne de caractères qui est ensuite décompressé, et de travailler avec le fichier de l'objet directement.Addendum: BZ2File est probablement pas applicable dans votre cas, car il nécessite un nom de fichier en argument. Ce que vous avez besoin est quelque chose comme un objet de fichier vue avec lecture intégrée limite, comparable à ZipExtFile mais en utilisant BZ2Decompressor pour la décompression.
Mon point principal ici, c'est que votre code doit être modifié pour effectuer un plus itératif traitement de vos données au lieu de l'aspirer dans l'ensemble et de le diviser à nouveau par la suite.
OriginalL'auteur Bernd Petersohn