【问题标题】:Understanding Python profile output了解 Python 配置文件输出
【发布时间】:2010-11-30 23:29:31
【问题描述】:

我正在尝试使用 Python 分析器来加速我的代码。我已经能够识别出几乎所有时间都花在了哪个特定函数上,但我无法弄清楚时间花在那个函数的什么地方。

下面是配置文件输出,它显示“appendBallot”是罪魁祸首,消耗了近 116 秒。在下面,我有“appendBallot”的代码。

我无法从配置文件输出中弄清楚,我需要优化“appendBallot”的哪一部分,因为下一个最高时间条目不到一秒。我相信你们中的许多人都可以从我的代码中告诉我,但我想了解如何从配置文件输出中获取该信息。任何帮助将不胜感激。

配置文件输出:

  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)

【问题讨论】:

  • 确实是 assert() 一直在占用。我想知道 Python 分析器是否会忽略 assert() 语句,因为如果使用 -O 运行代码,它们将不会被执行,
  • 感谢所有有用的答案。
  • Python 分析器不会忽略 assert /statements/ ,就像它忽略方法中的所有其他 /statements/ 一样。写 assert(expression) 而不仅仅是 assert expression 不会把它变成一个可以被忽略的函数调用。

标签: python profiling profile


【解决方案1】:

是的,我也遇到了同样的问题。

我知道解决此问题的唯一方法是将您的大函数包装成几个较小的函数调用。这将允许分析器考虑每个较小的函数调用。

很有趣,这样做的过程(对我来说,无论如何)让效率低下的地方一目了然,所以我什至不必运行分析器。

【讨论】:

  • 您是对的(在实际意义上),但“以不同的方式重写代码,以便探查器的缺陷不那么明显”似乎是错误的答案。
【解决方案2】:

我查看了您的代码,看起来您进行了很多函数调用和属性查找,作为您“检查”的一部分,或者在跳跃前向前看。您还有很多专门用于跟踪相同条件的代码,即许多代码位都在寻找创建“唯一”ID。

而不是尝试为每张选票分配某种独特的字符串,你不能只是 使用 ballotID(整数?)

现在您可以拥有一个映射 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)

您也许可以解决一些对字典缺少给定键的担忧 通过使用 defaultdict (来自集合模块)。 collection docs

编辑为完整起见,我将包含一个 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>])]

【讨论】:

    【解决方案3】:

    我在我的代码中使用了this decorator,它帮助我完成了 pyparsing 调优工作。

    【讨论】:

      【解决方案4】:

      我会支持 Fragsworth 说您希望将您的功能拆分为更小的功能。

      话虽如此,您正在正确读取输出:tottime 是要观看的。

      现在你的减速可能在哪里:

      由于似乎有 100000 次调用 appendBallot,并且没有任何明显的循环,我建议它在您的断言中。因为你正在执行:

      assert(ballotID not in self.ballotIDs)
      

      这实际上是一个循环。因此,第一次调用此函数时,它将遍历一个(可能为空的)数组,然后断言是否找到该值。第 100000 次它将遍历整个数组。

      这里实际上存在一个可能的错误:如果删除了选票,则添加的下一张选票将与最后添加的选票具有相同的 id(除非那张选票已被删除)。我认为你最好使用一个简单的计数器。这样,您每次添加选票时都可以增加它。或者,您可以使用 UUID 来获取唯一 ID。

      或者,如果您正在考虑某种程度的持久性,请使用 ORM,并让它生成 ID,并为您进行唯一检查。

      【讨论】:

      • 他没有“呼唤”任何东西; assert 是一个语句。
      【解决方案5】:

      在这段小代码中有两个问题:

      # 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 语句将导致二次行为。由于您根本没有为您的数据结构提供任何文档,因此不可能是规定性的,但如果出现的顺序无关紧要,您可以使用集合而不是列表。

      其次,逻辑(在没有关于 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
      

      其他cmets:

      使用key in adict 代替adict.has_key(key)——它更快、更好看。

      您可能想考虑检查您的数据结构......它们似乎有点巴洛克式的;构建它们可能需要相当多的 CPU 时间。

      【讨论】:

        【解决方案6】:

        Profilers 可以是这样的。我使用的方法是this。它很快就解决了问题的核心。

        【讨论】:

        • 虽然有很多优秀的 cmets,但这是为我所需要的提供最快和最简单的答案的一个。
        • @George:我知道你的意思,但它有点大。如果链接失效,我会处理它。
        猜你喜欢
        • 2013-06-26
        • 1970-01-01
        • 2014-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 2011-10-19
        • 1970-01-01
        相关资源
        最近更新 更多