7

Python プロファイラーを使用してコードを高速化しようとしています。ほぼすべての時間が費やされている特定の機能を特定できましたが、その機能のどこで時間が費やされているかはわかりません。

以下にプロファイル出力を示します。これは、「appendBallot」が主な原因であり、116 秒近く消費していることを示しています。さらに下に、「appendBallot」のコードがあります。

プロファイル出力からは、「appendBallot」のどの部分を最適化する必要があるかわかりません。次に高い時間のエントリは 1 秒未満です。多くの人が私のコードからだけ教えてくれると思いますが、プロファイル出力からその情報を取得する方法を理解したいと思います. どんな助けでも大歓迎です。

プロファイル出力:

  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)

コード:

  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)
4

6 に答える 6

7

ええ、私も同じ問題に遭遇しました。

これを回避する唯一の方法は、大きな関数をいくつかの小さな関数呼び出しにラップすることです。これにより、プロファイラーは小さな関数呼び出しのそれぞれを考慮することができます。

興味深いことに、これを行うプロセスによって (とにかく私にとっては) 非効率な場所が明らかになったので、プロファイラーを実行する必要さえありませんでした。

于 2009-09-24T03:58:55.850 に答える
5

関数をより小さな関数に分割したいと言って、Fragsworthをサポートします。

そうは言っても、あなたは出力を正しく読んでいます:tottimeは注目すべきものです。

今、あなたの減速が起こりそうな場所のために:

appendBallotへの呼び出しが100000あるようで、明らかなループがないので、それがアサーションにあることをお勧めします。実行しているため:

assert(ballotID not in self.ballotIDs)

これは実際にはループとして機能します。したがって、この関数を初めて呼び出すと、(おそらく空の)配列を反復処理し、値が見つかった場合はアサートします。100000回目は、アレイ全体を反復処理します。

実際、ここには潜在的なバグがあります。投票用紙が削除された場合、次に追加された投票用紙は、最後に追加された投票用紙と同じIDになります(削除された投票用紙でない限り)。シンプルなカウンターを使ったほうがいいと思います。そうすれば、投票用紙を追加するたびにそれを増やすことができます。または、UUIDを使用して一意のIDを取得することもできます。

または、ある程度の永続性を検討している場合は、ORMを使用して、IDの生成と一意のチェックを実行します。

于 2009-09-24T08:01:11.937 に答える
5

私はあなたのコードを見てきましたが、「チェック」の一環として、または飛躍する前に先を見越して、多くの関数呼び出しと属性検索を行っているようです。また、同じ条件を追跡するための専用のコードがたくさんあります。つまり、「一意の」ID を作成するためのコードがたくさんあります。

各投票にある種の一意の文字列を割り当てようとする代わりに、投票 ID (整数値?) を使用できませんでしたか?

これで、ballotID と実際の投票オブジェクトをマッピングする辞書 (uniqueBallotIDs) を持つことができます。

プロセスは次のようになります。

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)

( collections モジュールの) defaultdict を使用することで、特定のキーが欠落しているディクショナリに関する懸念の一部を処理できる場合があります。コレクション ドキュメント

完全を期すために編集し、defaultdict の使用例を含めます。

>>> 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>])]
于 2009-09-24T05:25:49.800 に答える
5

プロファイラーはそのようなものです。私が使用する方法はこれです。あっという間に問題の核心に迫ります。

于 2009-09-24T15:26:50.727 に答える
4

私は自分のコードでこのデコレータを使用しており、パイパーシングのチューニング作業に役立ちました。

于 2009-09-24T06:12:26.987 に答える
2

この小さなコードスライスには2つの問題があります。

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

まず、self.ballotIDsはリストであるように見えるため、assertステートメントは2次動作を引き起こします。データ構造に関するドキュメントをまったく提供しなかったため、規範的なものにすることはできませんが、表示の順序が重要でない場合は、リストの代わりにセットを使用できます。

第二に、ロジック(ballotIDが何であるか、およびnot-None ballotID argが何を意味するかについてのドキュメントがない場合)は深刻なバグがあるようです。

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

他のコメント:

の代わりにadict.has_key(key)、を使用してkey in adictください-それはより速く、より良く見えます。

データ構造のレビューを検討することをお勧めします...それらはわずかにバロック的であるように見えます。それらの構築にはかなりのCPU時間がかかる場合があります。

于 2009-09-24T11:12:31.863 に答える