Project Euler和其他编码竞赛通常有最长的运行时间,或者人们吹嘘他们的特定解决方案运行速度有多快。对于Python,有时方法有些笨拙——即向__main__添加计时代码。
描述Python程序运行时间的好方法是什么?
Project Euler和其他编码竞赛通常有最长的运行时间,或者人们吹嘘他们的特定解决方案运行速度有多快。对于Python,有时方法有些笨拙——即向__main__添加计时代码。
描述Python程序运行时间的好方法是什么?
当前回答
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}
编辑:更新了2013年PyCon视频资源的链接,标题为Python评测也可以通过YouTube。
其他回答
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配置文件
apt-get install python-profiler
如果输出到文件,可以使用以下工具获得良好的可视化效果
PyCallGraph:创建调用图图像的工具安装:
pip install pycallgraph
run:
pycallgraph mine.py args
视图:
gimp pycallgraph.png
你可以使用任何你喜欢的方式来查看png文件,我使用了gimp不幸的是,我经常
dot:graph对于cairo渲染器位图太大。缩放0.257079以适合
这使我的图像变得难以使用。所以我通常创建svg文件:
pycallgraph -f svg -o pycallgraph.svg mine.py <args>
PS>确保安装graphviz(提供点程序):
pip install graphviz
使用gprof2dot通过@maxy/@quodlibetor绘制替代图形:
pip install gprof2dot
python -m cProfile -o profile.pstats mine.py
gprof2dot -f pstats profile.pstats | dot -Tsvg -o mine.svg
我的方法是使用雅皮语(https://github.com/sumerc/yappi). 它与RPC服务器结合使用特别有用,在RPC服务器中(甚至只是为了调试),您可以注册方法来启动、停止和打印配置信息,例如,通过以下方式:
@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 = '\n'
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)) + "\n"
log.log(1000, "\nname" + ''.ljust(maxNameLen - 4) + " ncall \tttot \ttsub")
log.log(1000, statPrint)
然后,当您的程序工作时,您可以通过调用startProfiler RPC方法随时启动探查器,并通过调用printProfiler(或修改RPC方法将其返回给调用者)将探查信息转储到日志文件中,并获得这样的输出:
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\rfc822.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
它可能对短脚本不太有用,但有助于优化服务器类型的进程,特别是考虑到printProfiler方法可以随时间多次调用,以分析和比较例如不同的程序使用场景。
在较新版本的yappi中,以下代码将起作用:
@staticmethod
def printProfile():
yappi.get_func_stats().print_all()
对于像austin这样的统计分析器,不需要检测,这意味着您可以简单地使用
austin python3 my_script.py
原始输出不是很有用,但您可以将其传输到flamegraph.pl以获得该数据的火焰图表示,该火焰图提供了时间(以微秒为单位的实时)的细分。
austin python3 my_script.py | flamegraph.pl > my_script_profile.svg
或者,您也可以使用web应用程序Speedscope.app快速可视化收集的样本。如果您安装了pprof,还可以获取austin python(例如,pipx安装austin python)并使用austin2prof转换为pprof格式。
然而,如果您安装了VS Code,您可以使用Austin扩展来获得更交互式的体验,包括源代码热图、顶级函数和收集的调用堆栈
如果您想使用终端,也可以使用TUI,它也具有实时图形模式:
我发现,如果您不想使用命令行选项,该功能快速且易于使用。
要使用,只需在要分析的每个函数上方添加@profile。
def profile(fnc):
"""
Profiles any function in following class just by adding @profile above function
"""
import cProfile, pstats, io
def inner (*args, **kwargs):
pr = cProfile.Profile()
pr.enable()
retval = fnc (*args, **kwargs)
pr.disable()
s = io.StringIO()
sortby = 'cumulative' #Ordered
ps = pstats.Stats(pr,stream=s).strip_dirs().sort_stats(sortby)
n=10 #reduced the list to be monitored
ps.print_stats(n)
#ps.dump_stats("profile.prof")
print(s.getvalue())
return retval
return inner
每个函数的输出如下
Ordered by: cumulative time
List reduced from 38 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.002 0.002 3151212474.py:37(get_pdf_page_count)
1 0.000 0.000 0.002 0.002 fitz.py:3604(__init__)
1 0.001 0.001 0.001 0.001 {built-in method fitz._fitz.new_Document}
1 0.000 0.000 0.000 0.000 fitz.py:5207(__del__)
1 0.000 0.000 0.000 0.000 {built-in method fitz._fitz.delete_Document}
1 0.000 0.000 0.000 0.000 fitz.py:4816(init_doc)
1 0.000 0.000 0.000 0.000 fitz.py:5197(_reset_page_refs)
1 0.000 0.000 0.000 0.000 fitz.py:4821(<listcomp>)
11 0.000 0.000 0.000 0.000 fitz.py:4054(_getMetadata)
1 0.000 0.000 0.000 0.000 weakref.py:241(values)
一个很好的评测模块是line_profiler(使用kernprof.py脚本调用)。它可以在这里下载。
我的理解是,cProfile只提供每个函数花费的总时间的信息。因此,单独的代码行是不定时的。这是科学计算中的一个问题,因为通常一条线会花费很多时间。而且,我记得,cProfile没有抓住我在say numpy.dot上花费的时间。