2009-09-24 12 views
6

Ich versuche den Python-Profiler zu verwenden, um meinen Code zu beschleunigen. Ich konnte die spezifische Funktion identifizieren, bei der fast die gesamte Zeit verbraucht wird, aber ich kann nicht herausfinden, wo in dieser Funktion die Zeit verbracht wird.Python-Profil-Ausgabe verstehen

Unten habe ich die Profilausgabe, die zeigt, dass "appendBallot" der Hauptschuldige ist und verbraucht fast 116 Sekunden. Weiter unten habe ich den Code für "appendBallot".

Ich kann nicht aus der Profilausgabe herausfinden, welchen Teil von "appendBallot" ich optimieren muss, da der nächsthöhere Zeiteintrag weniger als eine Sekunde ist. Ich bin mir sicher, dass viele von Ihnen mir nur aus meinem Code erzählen könnten, aber ich würde gerne verstehen, wie man diese Informationen aus der Profilausgabe holt. Jede Hilfe würde sehr geschätzt werden.

Profil Ausgang:

ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 116.168 116.168 <string>:1(<module>) 
     1 0.001 0.001 116.168 116.168 {execfile} 
     1 0.003 0.003 116.167 116.167 foo.py:1(<module>) 
     1 0.000 0.000 116.139 116.139 ballots.py:330(loadKnown) 
     1 0.000 0.000 116.109 116.109 plugins.py:148(load) 
     1 0.196 0.196 116.108 116.108 BltBallotLoader.py:37(loadFile) 
    100000 114.937 0.001 115.912 0.001 ballots.py:133(appendBallot) 
    100000 0.480 0.000 0.790 0.000 ballots.py:117(newBallot) 
    316668 0.227 0.000 0.310 0.000 ballots.py:107(getNumCandidates) 
417310/417273 0.111 0.000 0.111 0.000 {len} 
    200510 0.071 0.000 0.071 0.000 {method 'append' of 'list' objects} 
    99996 0.045 0.000 0.045 0.000 {method 'add' of 'set' objects} 
    100000 0.042 0.000 0.042 0.000 {method 'has_key' of 'dict' objects} 
     1 0.000 0.000 0.030 0.030 plugins.py:202(getLoaderPluginClasses) 
     1 0.000 0.000 0.030 0.030 plugins.py:179(getPluginClasses) 
     1 0.000 0.000 0.030 0.030 plugins.py:205(getLoaderPluginClass) 
     3 0.016 0.005 0.029 0.010 {__import__} 
     1 0.022 0.022 0.025 0.025 ballots.py:1(<module>) 
     1 0.010 0.010 0.013 0.013 BltBallotLoader.py:1(<module>) 
     7 0.000 0.000 0.003 0.000 re.py:227(_compile) 

Code:

def appendBallot(self, ballot, ballotID=None): 
    "Append a ballot to this Ballots object." 

    # String representation of ballot for determining whether ballot is unique 
    ballotString = str(list(ballot)) 

    # Ballot as the appropriate array to conserve memory 
    ballot = self.newBallot(ballot) 

    # Assign a ballot ID if one has not been given 
    if ballotID is None: 
     ballotID = len(self.ballotIDs) 
    assert(ballotID not in self.ballotIDs) 
    self.ballotIDs.append(ballotID) 

    # Check to see if we have seen this ballot before 
    if self.uniqueBallotsLookup.has_key(ballotString): 
     i = self.uniqueBallotsLookup[ballotString] 
     self.uniqueBallotIDs[i].add(ballotID) 
    else: 
     i = len(self.uniqueBallots) 
     self.uniqueBallotsLookup[ballotString] = i 
     self.uniqueBallots.append(ballot) 
     self.uniqueBallotIDs.append(set([ballotID])) 
    self.ballotOrder.append(i) 
+0

Es ist in der Tat die Assert(), die die ganze Zeit hogging. Ich frage mich, ob der Python-Profiler assert() -Anweisungen ignoriert, da sie nicht ausgeführt werden, wenn der Code mit -O, –

+0

, ausgeführt wird. Danke für alle hilfreichen Antworten. –

+0

Der Python-Profiler ignoriert 'assert'/statements nicht mehr als ignoriert alle anderen/Anweisungen/in der Methode. Das Schreiben von 'assert (expression)' anstelle von nur 'assert expression' macht es nicht zu einem Funktionsaufruf, der unigniert werden kann. –

Antwort

3

Profiler können so sein. Die Methode, die ich verwende, ist this. Es kommt schnell zum Kern des Problems.

+0

Obwohl es viele gute Kommentare gab, war dies die schnellste und einfachste Antwort auf das, was ich brauchte. –

+0

@Jeff. Ich bin froh, dass es geholfen hat. –

+0

@George: Ich weiß, was du meinst, aber es ist ein bisschen groß. Wenn der Link stirbt, werde ich damit umgehen. –

6

Ja ich auf das gleiche Problem kam auch.

Die einzige Möglichkeit, um dies zu umgehen, besteht darin, Ihre große Funktion in mehrere kleinere Funktionsaufrufe zu wickeln. Dadurch kann der Profiler jeden der kleineren Funktionsaufrufe berücksichtigen.

Interessant genug, der Prozess, dies zu tun (für mich sowieso) machte es offensichtlich, wo die Ineffizienzen waren, so dass ich nicht einmal den Profiler laufen musste.

+0

Sie haben Recht (im praktischen Sinne) und doch "schreiben Sie Ihren Code anders um, so dass die Mängel des Profilers weniger offensichtlich sind" scheint die falsche Antwort zu sein. – Basic

5

Ich habe mir Ihren Code angesehen, und es sieht so aus, als würden Sie viele Funktionsaufrufe und Attribut-Lookups als Teil Ihrer "Überprüfung" oder Vorausschau vor dem Springen machen. Sie haben auch viel Code dediziert, um die gleiche Bedingung zu verfolgen, d. H. Viele Code-Bits, die auf die Erstellung von "eindeutigen" IDs abzielen.

anstatt zu versuchen, irgendeine Art von eindeutiger Zeichenfolge zu jedem Stimmzettel zuweisen, man kann nicht nur verwenden, um den ballotID (eine ganze Zahl?)

jetzt können Sie einen Wörterbuch (uniqueBallotIDs) Mapping ballotID haben und die tatsächliches Stimmzettelobjekt.

der Prozess könnte wie folgt sein:

def appendBallot(self, ballot, ballotID=None): 
    if ballotID is None: 
     ballotID = self._getuniqueid() # maybe just has a counter? up to you. 
    # check to see if we have seen this ballot before. 
    if not self._isunique(ballotID): 
     # code for non-unique ballot ids. 
    else: 
     # code for unique ballot ids. 

    self.ballotOrder.append(i) 

Sie könnten in der Lage sein, einige Ihrer Sorgen um das Wörterbuch zu behandeln, indem ein defaultdict einen bestimmten Schlüssel fehlt (aus der Sammlung Modul). collection docs

bearbeiten auf Vollständigkeit werde ich eine Probe Nutzung des defaultdict umfassen:

>>> from collections import defaultdict    

>>> ballotIDmap = defaultdict(list) 
>>> ballotID, ballot = 1, object() # some nominal ballotID and object. 
>>> # I will now try to save my ballotID. 
>>> ballotIDmap[ballotID].append(ballot) 
>>> ballotIDmap.items() 
[(1, [<object object at 0x009BB950>])] 
3

Ich werde Frumsworth unterstützen, indem ich sage, dass Sie Ihre Funktion in kleinere aufteilen möchten.

Nachdem Sie das gesagt haben, lesen Sie die Ausgabe korrekt: die Totzeit ist die zu beobachtende.

Nun denn wo Ihre Verlangsamung ist wahrscheinlich:

Da es scheint 100000 Anrufe appendBallot zu sein, und es gibt keine offensichtlichen Loops, würde ich vorschlagen, es in Ihrem assert ist. Weil Sie ausgeführt werden:

assert(ballotID not in self.ballotIDs) 

Dies wird tatsächlich als eine Schleife fungieren. Wenn Sie diese Funktion das erste Mal aufrufen, durchläuft sie ein (wahrscheinlich leeres) Array und bestätigt dann, ob der Wert gefunden wurde. Das 100000ste Mal wird es durch das gesamte Array durchlaufen.

Und es gibt tatsächlich einen möglichen Fehler hier: Wenn ein Stimmzettel gelöscht wird, dann hätte der nächste hinzugefügte Stimmzettel die gleiche ID wie der zuletzt hinzugefügte Stimmzettel (es sei denn, dass der eine gestrichen wurde). Ich denke, Sie wären besser dran, einen einfachen Zähler zu verwenden. Auf diese Weise können Sie es jedes Mal erhöhen, wenn Sie einen Stimmzettel hinzufügen. Alternativ können Sie eine UUID verwenden, um eindeutige IDs zu erhalten.

Alternativ, wenn Sie auf ein gewisses Maß an Persistenz suchen, verwenden Sie ein ORM, und machen Sie es, um die ID-Generierung zu tun, und einzigartige Prüfung für Sie.

+0

Er "ruft" nichts an; Behauptung ist eine Aussage. –

+0

Mein schlechtes. Wird reparieren. –

2

Sie haben zwei Probleme in diesem kleinen Stück Code:

# Assign a ballot ID if one has not been given 
if ballotID is None: 
    ballotID = len(self.ballotIDs) 
assert(ballotID not in self.ballotIDs) 
self.ballotIDs.append(ballotID) 

Zunächst scheint es, dass self.ballotIDs eine Liste ist, so dass die assert-Anweisung wird quadratisches Verhalten führen. Da Sie für Ihre Datenstrukturen überhaupt keine Dokumentation angegeben haben, ist es nicht möglich, Vorschriften zu machen, aber wenn die Reihenfolge der Darstellung keine Rolle spielt, könnten Sie eine Menge anstelle einer Liste verwenden.

Zweitens ist die Logik (in Abwesenheit von Dokumentation auf welchem ​​ein ballotID dreht sich alles um, und was für eine nicht-None ballotID arg bedeutet) scheint ernst abgehört:

obj.appendBallot(ballota, 2) # self.ballotIDs -> [2] 
obj.appendBallot(ballotb) # self.ballotIDs -> [2, 1] 
obj.appendBallot(ballotc) # wants to add 2 but triggers assertion 

Sonstiges:

Verwenden Sie statt adict.has_key(key)key in adict - es ist schneller und sieht besser aus.

Vielleicht möchten Sie Ihre Datenstrukturen überprüfen ... sie scheinen etwas barock zu sein; Es kann ein wenig CPU-Zeit in Anspruch nehmen, um sie aufzubauen.