题 你如何描述一个脚本?


项目欧拉和其他编码竞赛通常有最长的运行时间或人们吹嘘他们的特定解决方案运行的速度。使用python,有时候这些方法有些笨拙 - 即添加时间码 __main__

分析python程序运行多长时间的好方法是什么?


976
2018-02-24 16:01


起源


项目euler程序不应该需要分析。你有一个算法在一分钟内工作,或者你有完全错误的算法。 “调整”很少适合。你通常需要采取一种新的方法。 - S.Lott
S.Lott:分析通常是确定哪些子程序很慢的有用方法。花费很长时间的子程序是算法改进的理想选择。 - stalepretzel
@stalepretzel,戴夫:几年前我写了那篇评论。我的立场得到纠正,并且已经回答了我自己的一些问题:)。但是,我仍然认为cProfile并不是那么好。 runsnakerun和pycallgraph对我来说更容易使用。 - gatoatigrado
如果你想知道有多长时间,为了竞争的目的,你不应该添加任何分析代码,因为这将减慢它。只需使用unix time 命令,它会告诉你有多长时间。 - dalore


答案:


Python包含一个名为的探查器 CPROFILE。它不仅可以给出总运行时间,还可以分别给出每个函数的时间,并告诉您每个函数被调用了多少次,这样可以很容易地确定应该在哪里进行优化。

您可以在代码中或从解释器中调用它,如下所示:

import cProfile
cProfile.run('foo()')

更有用的是,您可以在运行脚本时调用cProfile:

python -m cProfile myscript.py

为了使它更容易,我制作了一个名为'profile.bat'的小批处理文件:

python -m cProfile %1

所以我要做的就是运行:

profile euler048.py

我明白了:

1007 function calls in 0.061 CPU seconds

Ordered by: standard name
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.061    0.061 <string>:1(<module>)
 1000    0.051    0.000    0.051    0.000 euler048.py:2(<lambda>)
    1    0.005    0.005    0.061    0.061 euler048.py:2(<module>)
    1    0.000    0.000    0.061    0.061 {execfile}
    1    0.002    0.002    0.053    0.053 {map}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler objects}
    1    0.000    0.000    0.000    0.000 {range}
    1    0.003    0.003    0.003    0.003 {sum}

编辑:更新了PyCon 2013中标题为的视频资源的链接 Python分析
也可以通过YouTube


1078
2018-02-24 16:01



对结果进行排序也很有用,可以通过-s开关来完成,例如:'-s time'。您可以使用累积/名称/时间/文件排序选项。 - Jiri
不幸的是,你无法对总时间或累计时间进行排序,这是IMO的一个主要缺陷。 - Joe Shaw
值得注意的是,您可以使用魔术函数%prun(profile run)从ipython中使用cProfile模块。首先导入你的模块,然后使用%prun:import euler048调用main函数; %prun euler048.main() - RussellStewart
用于可视化cProfile转储(由 python -m cProfile -o <out.profile> <script>) RunSnakeRun,调用为 runsnake <out.profile> 非常宝贵。 - ikdc
@NeilG即使是python 3, cprofile 仍然值得推荐 过度 profile。 - trichoplax


前一段时间我做了 pycallgraph 从Python代码生成可视化。 编辑: 我已经更新了示例以使用最新版本。

之后 pip install pycallgraph 和安装 GraphViz的 您可以从命令行运行它:

pycallgraph graphviz -- ./mypythonscript.py

或者,您可以分析代码的特定部分:

from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

with PyCallGraph(output=GraphvizOutput()):
    code_to_profile()

这些中的任何一个都会产生一个 pycallgraph.png 文件类似于下图:

enter image description here


349
2017-08-06 05:37



你根据通话量着色吗?如果是这样,您应该根据时间进行着色,因为具有最多呼叫的功能并不总是花费最多时间的功能。 - red
@red您可以根据自己的喜好自定义颜色,甚至可以为每次测量自定义颜色。例如,红色表示呼叫,蓝色表示时间,绿色表示内存使用。 - Gerald Kaszuba
@Gerald,这不起作用。是Pip安装,安装良好,但它说我运行命令时找不到graphviz。我甚至做到了 apt-get install graphviz, 没有不同。使用'graphviz'运行终端显示未找到命令。我究竟做错了什么?我正在使用Linux Mint 15。 - Matt
得到这个错误Traceback (most recent call last): /pycallgraph.py", line 90, in generate output.done() File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 94, in done source = self.generate() File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 143, in generate indent_join.join(self.generate_attributes()), File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 169, in generate_attributes section, self.attrs_from_dict(attrs), ValueError: zero length field name in format - Ciasto piekarz
我更新了这一点,提到你需要安装GraphViz,以便按照描述的方式工作。在Ubuntu这只是 sudo apt-get install graphviz。 - mlissner


值得指出的是,使用探查器仅在主线程上工作(默认情况下),如果使用它们,则不会从其他线程获取任何信息。这可能是一个问题,因为它完全没有提到 剖析器文档

如果你还想要分析线程,你会想看看 threading.setprofile() 功能 在文档中。

你也可以创建自己的 threading.Thread 要做的子类:

class ProfiledThread(threading.Thread):
    # Overrides threading.Thread.run()
    def run(self):
        profiler = cProfile.Profile()
        try:
            return profiler.runcall(threading.Thread.run, self)
        finally:
            profiler.dump_stats('myprofile-%d.profile' % (self.ident,))

并使用它 ProfiledThread 类而不是标准的。它可能会给你更多的灵活性,但我不确定它是否值得,特别是如果你使用不使用你的类的第三方代码。


167
2017-12-17 16:30



我也没有在文档中看到对runcall的任何引用。看看cProfile.py,我不确定为什么你使用threading.Thread.run函数也不使用self作为参数。我原本希望看到一个参考 另一个 线程的运行方法在这里。 - PypeBros
它不在文档中,但它在模块中。看到 hg.python.org/cpython/file/6bf07db23445/Lib/cProfile.py#l140。这允许您分析特定的函数调用,在我们的例子中,我们想要分析Thread的 target 功能,这是什么的 threading.Thread.run() 呼叫执行。但正如我在答案中所说的,将子类化为Thread可能不值得,因为任何第三方代码都不会使用它,而是使用它 threading.setprofile()。 - Joe Shaw
用profiler.enable()和profiler.disable()包装代码似乎也运行得很好。这基本上就是runcall所做的,并且它不会强制执行任何数量的参数或类似的事情。 - PypeBros
我结合了自己的 stackoverflow.com/questions/10748118/... 同 ddaa.net/blog/python/lsprof-calltree 它有点工作;! - ) - Dima Tisnek
Joe,你知道Profiler如何在Python 3.4中使用asyncio吗? - Nick Chammas


python wiki是一个用于分析资源的好页面: http://wiki.python.org/moin/PythonSpeed/PerformanceTips#Profiling_Code

和python文档一样: http://docs.python.org/library/profile.html

如Chris Lawlor所示cProfile是一个很棒的工具,可以很容易地用于打印到屏幕上:

python -m cProfile -s time mine.py <args>

或提交:

python -m cProfile -o output.file mine.py <args>

PS>如果您使用的是Ubuntu,请确保安装python-profile

sudo apt-get install python-profiler 

如果输出到文件,则可以使用以下工具获得良好的可视化效果

PyCallGraph:一种创建调用图图像的工具
  安装:

 sudo pip install pycallgraph

跑:

 pycallgraph mine.py args

视图:

 gimp pycallgraph.png

您可以使用任何您喜欢的方式来查看png文件,我使用了gimp
不幸的是我常常得到

dot:图形对于cairo-renderer位图来说太大了。按比例缩小0.257079以适应

这让我的图像变得非常小。所以我通常创建svg文件:

pycallgraph -f svg -o pycallgraph.svg mine.py <args>

PS>确保安装graphviz(提供点程序):

sudo pip install graphviz

通过@maxy / @quodlibetor使用gprof2dot替代绘图:

sudo pip install gprof2dot
python -m cProfile -o profile.pstats mine.py
gprof2dot -f pstats profile.pstats | dot -Tsvg -o mine.svg

126
2017-10-08 00:04



gprof2dot 也可以做那些图表。我认为输出更好一点(例)。 - maxy
如果您使用OSX,还需要graphviz - Vaibhav Mishra
在我的Ubuntu 14.04安装上, sudo apt-get install gprof2dot 结果是 E: Unable to locate package gprof2dot 错误。但是,如果我跑 sudo pip install gprof2dot 它工作正常。你确定你的意思吗? apt-get 并不是 pip install? - Michael Osl
我做了编辑,PS:如果你使用的是python的anaconda发行版,你可能想要研究python virtualenv或conda。他们保留它,以便您不必污染您使用正在为一个项目下载的包创建python站点包。 - brent.payne
你是对的 pip install 不 apt-get install,我在上面改了。 - brent.payne


@Maxy的评论 这个答案 帮助我足够的,我认为它应该得到自己的答案:我已经有cProfile生成的.pstats文件,我不想用pycallgraph重新运行,所以我用过 gprof2dot,并得到了漂亮的svgs:

$ sudo apt-get install graphviz
$ git clone https://github.com/jrfonseca/gprof2dot
$ ln -s "$PWD"/gprof2dot/gprof2dot.py ~/bin
$ cd $PROJECT_DIR
$ gprof2dot.py -f pstats profile.pstats | dot -Tsvg -o callgraph.svg

和BLAM!

它使用点(与pycallgraph使用的相同),因此输出看起来相似。我得到的印象是gprof2dot丢失的信息较少:

gprof2dot example output


113
2017-12-11 23:16



好的方法,非常好用,因为你可以在Chrome等中查看SVG并向上/向下扩展它。第三行有拼写错误,应该是:ln -s pwd/gprof2dot/gprof2dot.py $ HOME / bin(或在大多数shell中使用ln -s $ PWD / gprof2dot / gprof2dot.py~ / bin - 在第一个版本中将严重重音视为格式化)。 - RichVel
啊,好点。我明白了 ln几乎每次都是错误的论证。 - quodlibetor
诀窍是要记住ln和cp具有相同的参数顺序 - 将其视为'将file1复制到file2或dir2,但是建立一个链接' - RichVel
这是有道理的,我认为在联机帮助页中使用“TARGET”会让我失望。 - quodlibetor
ln -s from;) - Ярослав Рахматуллин


我遇到了一个叫做的方便工具 SnakeViz 在研究这个主题时。 SnakeViz是一个基于Web的分析可视化工具。它非常易于安装和使用。我使用它的常用方法是生成一个stat文件 %prun 然后在SnakeViz中进行分析。

使用的主要技术是 森伯斯特图表 如下所示,其中函数调用的层次被安排为以角度宽度编码的弧和时间信息的层。

最好的是你可以与图表互动。例如,要放大一个可以单击一个弧,并且弧及其后代将被放大为新的旭日以显示更多细节。

enter image description here


41
2018-05-25 08:06





另外值得一提的是GUI cProfile转储查看器 RunSnakeRun。它允许您进行排序和选择,从而放大程序的相关部分。图中矩形的大小与所花费的时间成比例。如果将鼠标悬停在矩形上,则会突出显示表格中的调用以及地图上的任何位置。双击矩形时,它会放大该部分。它将显示谁调用该部分以及该部分调用的内容。

描述性信息非常有用。它显示了该位的代码,在处理内置库调用时可能会有所帮助。它告诉您查找代码的文件和行。

也想指出OP说'剖析',但看起来他的意思是“时机”。请记住,在分析时程序运行速度会变慢。

enter image description here


33
2018-02-22 16:18





我觉得 cProfile 非常适合分析,而 kcachegrind 非常适合可视化结果。该 pyprof2calltree 在between之间处理文件转换。

python -m cProfile -o script.profile script.py
pyprof2calltree -i script.profile -o script.calltree
kcachegrind script.calltree

要安装所需的工具(至少在Ubuntu上):

apt-get install kcachegrind
pip install pyprof2calltree

结果:

Screenshot of the result


30
2018-05-11 08:32



Mac用户安装 brew install qcachegrind 和各自的代表 kcachegrind  同 qcachegrind  在成功分析的描述中。 - Kevin Katzke


pprofile

line_profiler (已在此处展示)也受到启发 pprofile,描述为:

线粒度,线程感知确定性和统计纯python   探查

它提供了线粒度 line_profiler,是纯Python,可以用作独立命令或模块,甚至可以生成可以轻松分析的callgrind格式文件 [k|q]cachegrind

vprof

还有 vprof,一个Python包描述为:

[...]为各种Python程序特性(如运行时和内存使用情况)提供丰富的交互式可视化。

heatmap


28
2018-03-02 11:36





一个很好的分析模块是line_profiler(使用脚本kernprof.py调用)。它可以下载 这里

我的理解是cProfile只提供有关每个函数花费的总时间的信息。所以各行代码都没有定时。这是科学计算中的一个问题,因为通常一条线路可能需要花费很多时间。另外,正如我记得的那样,cProfile并没有抓住我在numpy.dot上花费的时间。


27
2017-10-20 16:05