How can you profile a Python script?
ProjectEuler和其他编码竞赛通常有最长的运行时间,或者人们吹嘘他们的特定解决方案运行的速度有多快。对于python,有时这些方法有些笨拙——即向
什么是一个好的方法来描述运行一个python程序需要多长时间?
python包含一个名为cprofile的分析器。它不仅给出了总的运行时间,而且还分别对每个函数进行了多次调用,并告诉您每个函数被调用了多少次,这样就很容易确定应该在哪里进行优化。
您可以从代码内部或从解释器调用它,如下所示:
1 2 | import cProfile cProfile.run('foo()') |
更有用的是,您可以在运行脚本时调用cprofile:
1 | python -m cProfile myscript.py |
为了更简单,我制作了一个名为"profile.bat"的小批处理文件:
1 | python -m cProfile %1 |
所以我要做的就是跑步:
1 | profile euler048.py |
我得到这个:
1 2 3 4 5 6 7 8 9 10 11 12 | 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。
不久前,我制作了
在
1 | pycallgraph graphviz -- ./mypythonscript.py |
或者,您可以分析代码的特定部分:
1 2 3 4 5 | from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutput with PyCallGraph(output=GraphvizOutput()): code_to_profile() |
其中任何一个都将生成一个与下图类似的
值得指出的是,使用探查器只在主线程上工作(默认情况下),如果使用其他线程,就不会从中获得任何信息。这可能有点难懂,因为在探查器文档中完全没有提到它。
如果您还想分析线程,那么您需要查看文档中的
您还可以创建自己的
1 2 3 4 5 6 7 8 | 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,)) |
用那个
python wiki是一个很好的分析资源页面:http://wiki.python.org/moin/pythonspeed/performancetips分析代码
与python文档一样:http://docs.python.org/library/profile.html网站
如Chris Lawlor所示,cprofile是一个很好的工具,可以很容易地用于打印到屏幕上:
1 | python -m cProfile -s time mine.py |
或归档:
1 | python -m cProfile -o output.file mine.py |
ps>如果您使用的是ubuntu,请确保安装python概要文件。
1 | sudo apt-get install python-profiler |
如果输出到文件,可以使用以下工具获得良好的可视化效果
pycallgraph:创建调用图图像的工具安装:
1 | sudo pip install pycallgraph |
运行:
1 | pycallgraph mine.py args |
观点:
1 | gimp pycallgraph.png |
你可以使用任何你想查看的png文件,我使用了gimp不幸的是我经常
点:图形对于cairo渲染器位图太大。缩放0.257079以适应
这使我的图像非常小。因此,我通常创建SVG文件:
1 | pycallgraph -f svg -o pycallgraph.svg mine.py |
ps>确保安装graphviz(提供点程序):
1 | sudo pip install graphviz |
通过@maxy/@quotlibetor使用gprof2dot的替代图形:
1 2 3 | sudo pip install gprof2dot python -m cProfile -o profile.pstats mine.py gprof2dot -f pstats profile.pstats | dot -Tsvg -o mine.svg |
@Maxy对这个答案的评论帮了我很大的忙,我认为它应该有自己的答案:我已经生成了cprofile.pstats文件,我不想用pycallgraph重新运行这些文件,所以我使用了gprof2dot,得到了漂亮的svg:
1 2 3 4 5 | $ 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会损失更少的信息,尽管:
我在研究这个话题时遇到了一个叫做snakeviz的方便工具。snakeviz是一个基于Web的分析可视化工具。它很容易安装和使用。我使用它的通常方法是用
使用的主要VIZ技术是sunburst图表,如下图所示,其中函数调用的层次结构被安排为以角度宽度编码的弧层和时间信息。
最好的是你可以和图表交互。例如,要放大,可以单击一个弧,该弧及其子体将作为新的阳光束放大以显示更多详细信息。
我认为
1 2 3 | python -m cProfile -o script.profile script.py pyprof2calltree -i script.profile -o script.calltree kcachegrind script.calltree |
要安装所需的工具(至少在Ubuntu上):
1 2 | apt-get install kcachegrind pip install pyprof2calltree |
结果:
还值得一提的是gui cprofile dump viewer runsnakerun。它允许您进行排序和选择,从而放大程序的相关部分。图片中矩形的大小与所用时间成正比。如果将鼠标悬停在一个矩形上,它将突出显示在表中以及地图上的所有位置调用的矩形。当您双击一个矩形时,它会放大该部分。它将显示谁调用该部分以及该部分调用什么。
描述性信息非常有用。它向您显示了该位的代码,当您处理内置库调用时,这些代码会很有帮助。它告诉您查找代码的文件和行。
还想指出的是,OP说的"分析",但似乎他是指"时机"。记住,程序在分析时运行速度会变慢。
一个好的分析模块是line_profiler(使用脚本kernprof.py调用)。它可以在这里下载。
我的理解是,cprofile只提供每个函数所花费的总时间的信息。所以单独的代码行没有计时。这是科学计算中的一个问题,因为通常一条线需要花费很多时间。而且,正如我所记得的,cprofile没有赶上我在say numpy.dot的时间。
剖面图
Line-granularity, thread-aware deterministic and statistic pure-python
profiler
它提供了行粒度,如
还有vprof,一个python包,描述如下:
[...] providing rich and interactive visualizations for various Python program characteristics such as running time and memory usage.
最简单、最快捷的方法来找到所有时间都要去的地方。
1 2 3 4 5 | 1. pip install snakeviz 2. python -m cProfile -o temp.dat <PROGRAM>.py 3. snakeviz temp.dat |
在浏览器中绘制饼图。最大的部分是问题函数。很简单。
我最近为可视化python运行时和导入配置文件创建了tuna;这在这里可能会有所帮助。
安装与
1 | pip3 install tuna |
创建运行时配置文件
1 | python -mcProfile -o program.prof yourfile.py |
或导入配置文件(需要python 3.7以上版本)
1 | python -X importprofile yourfile.py 2> import.log |
然后在文件上运行tuna
1 | tuna program.prof |
有很多很好的答案,但它们要么使用命令行,要么使用一些外部程序来分析和/或排序结果。
我真的错过了在我的IDE(EclipsePydev)中使用的一些方法,而不需要接触命令行或安装任何东西。就在这里。
不带命令行的分析1 2 3 4 5 6 7 8 9 10 11 | def count(): from math import sqrt for x in range(10**5): sqrt(x) if __name__ == '__main__': import cProfile, pstats cProfile.run("count()","{}.profile".format(__file__)) s = pstats.Stats("{}.profile".format(__file__)) s.strip_dirs() s.sort_stats("time").print_stats(10) |
有关更多信息,请参阅文档或其他答案。
根据Joe Shaw关于多线程代码不能按预期工作的回答,我认为cprofile中的
在virtaal的源代码中,有一个非常有用的类和修饰符,它可以使分析(即使对于特定的方法/函数)非常容易。然后可以在kcachegrind中非常舒适地查看输出。
CProfile非常适合快速分析,但大多数时候它都以错误结束。函数runctx通过正确初始化环境和变量来解决这个问题,希望它对某些人有用:
1 2 | import cProfile cProfile.runctx('foo()', None, locals()) |
我的方法是使用yappi(https://code.google.com/p/yappi/)。它与一个RPC服务器结合起来特别有用,在该服务器中(甚至只是为了调试)注册方法来启动、停止和打印分析信息,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | @staticmethod def startProfiler(): yappi.start() @staticmethod def stopProfiler(): yappi.stop() @staticmethod def printProfiler(): stats = yappi.get_stats(yappi.SORTTYPE_TTOT, yappi.SORTORDER_DESC, 20) statPrint = ' ' namesArr = [len(str(stat[0])) for stat in stats.func_stats] log.debug("namesArr %s", str(namesArr)) maxNameLen = max(namesArr) log.debug("maxNameLen: %s", maxNameLen) for stat in stats.func_stats: nameAppendSpaces = [' ' for i in range(maxNameLen - len(stat[0]))] log.debug('nameAppendSpaces: %s', nameAppendSpaces) blankSpace = '' for space in nameAppendSpaces: blankSpace += space log.debug("adding spaces: %s", len(nameAppendSpaces)) statPrint = statPrint + str(stat[0]) + blankSpace +"" + str(stat[1]).ljust(8) +"\t" + str( round(stat[2], 2)).ljust(8 - len(str(stat[2]))) +"\t" + str(round(stat[3], 2)) +" " log.log(1000," name" + ''.ljust(maxNameLen - 4) +" ncall \tttot \ttsub") log.log(1000, statPrint) |
然后,当您的程序运行时,您可以随时通过调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 2014-02-19 16:32:24,128-|SVR-MAIN |-(Thread-3 )-Level 1000: name ncall ttot tsub 2014-02-19 16:32:24,128-|SVR-MAIN |-(Thread-3 )-Level 1000: C:\Python27\lib\sched.py.run:80 22 0.11 0.05 M:\02_documents\_repos\09_aheadRepos\apps\ahdModbusSrv\pyAheadRpcSrv\xmlRpc.py.iterFnc:293 22 0.11 0.0 M:\02_documents\_repos\09_aheadRepos\apps\ahdModbusSrv\serverMain.py.makeIteration:515 22 0.11 0.0 M:\02_documents\_repos\09_aheadRepos\apps\ahdModbusSrv\pyAheadRpcSrv\PicklingXMLRPC.py._dispatch:66 1 0.0 0.0 C:\Python27\lib\BaseHTTPServer.py.date_time_string:464 1 0.0 0.0 c:\users\zasiec~1\appdata\local\temp\easy_install-hwcsr1\psutil-1.1.2-py2.7-win32.egg.tmp\psutil\_psmswindows.py._get_raw_meminfo:243 4 0.0 0.0 C:\Python27\lib\SimpleXMLRPCServer.py.decode_request_content:537 1 0.0 0.0 c:\users\zasiec~1\appdata\local\temp\easy_install-hwcsr1\psutil-1.1.2-py2.7-win32.egg.tmp\psutil\_psmswindows.py.get_system_cpu_times:148 4 0.0 0.0 <string>.__new__:8 220 0.0 0.0 C:\Python27\lib\socket.py.close:276 4 0.0 0.0 C:\Python27\lib\threading.py.__init__:558 1 0.0 0.0 <string>.__new__:8 4 0.0 0.0 C:\Python27\lib\threading.py.notify:372 1 0.0 0.0 C:\Python27\lib fc822.py.getheader:285 4 0.0 0.0 C:\Python27\lib\BaseHTTPServer.py.handle_one_request:301 1 0.0 0.0 C:\Python27\lib\xmlrpclib.py.end:816 3 0.0 0.0 C:\Python27\lib\SimpleXMLRPCServer.py.do_POST:467 1 0.0 0.0 C:\Python27\lib\SimpleXMLRPCServer.py.is_rpc_path_valid:460 1 0.0 0.0 C:\Python27\lib\SocketServer.py.close_request:475 1 0.0 0.0 c:\users\zasiec~1\appdata\local\temp\easy_install-hwcsr1\psutil-1.1.2-py2.7-win32.egg.tmp\psutil\__init__.py.cpu_times:1066 4 0.0 0.0 |
它可能对短脚本不太有用,但有助于优化服务器类型的进程,特别是考虑到随着时间的推移,可以多次调用
pyvmmonitor是在python中处理分析的一个新工具:http://www.pyvmmonitor.com/
它有一些独特的特点,例如
- 将探查器附加到正在运行的(cpython)程序
- Yappi集成的按需分析
- 不同机器上的配置文件
- 多进程支持(多进程、django…)
- 实时采样/CPU视图(带时间范围选择)
- 通过cprofile/profile集成进行确定性分析
- 分析现有的PSTAT结果
- 打开点文件
- 编程API访问
- 按方法或行对样本分组
- Pydev集成
- Pycharm集成
注意:它是商业的,但对于开源是免费的。
Ever want to know what the hell that python script is doing? Enter the
Inspect Shell. Inspect Shell lets you print/alter globals and run
functions without interrupting the running script. Now with
auto-complete and command history (only on linux).Inspect Shell is not a pdb-style debugger.
https://github.com/amoffat/inspect-shell
你可以用那个(还有你的手表)。
这取决于您想从分析中看到什么。简单时间度量可以通过(bash)给出。
1 | time python python_prog.py |
即使是'/usr/bin/time'也可以使用'--verbose'标志输出详细的度量。
为了检查每个函数给出的时间度量,并更好地了解在函数上花费了多少时间,可以在python中使用内置的cprofile。
进入更详细的指标,比如性能,时间并不是唯一的指标。你可以担心内存、线程等。分析选项:1。line-profiler是另一个profiler,通常用于逐行查找计时指标。2。内存分析器是一种分析内存使用情况的工具。三。heapy(来自项目guppy)描述如何使用堆中的对象。
这些是我常用的。但是,如果你想了解更多,试试看这本书这是一本很好的书,从表演开始。您可以转到有关使用Cython和JIT(及时)编译的Python的高级主题。
要添加到https://stackoverflow.com/a/582337/1070617,
我编写的这个模块允许您使用CProfile并轻松查看其输出。更多信息请访问:https://github.com/ymichael/cprofilev
1 2 | $ python -m cprofilev /your/python/program # Go to http://localhost:4000 to view collected statistics. |
另请参见:http://ymichael.com/2014/03/08/profileing-python-with-cprofile.html,了解如何理解收集的统计信息。
还有一个叫做
pypi中的版本有点旧,因此可以通过指定git存储库将其与
1 | pip install git+git://github.com/bos/statprof.py@1a33eba91899afe17a8b752c6dfdec6f05dd0c01 |
您可以这样运行它:
1 2 3 4 | import statprof with statprof.profile(): my_questionable_function() |
另请参阅https://stackoverflow.com/a/10333592/320036
当我不是服务器的根用户时,我使用lsprofcalltree.py并按如下方式运行我的程序:
1 | python lsprofcalltree.py -o callgrind.1 test.py |
然后我可以用任何与callgrind兼容的软件打开报告,比如qcachegrind。