Project Euler和其他编码竞赛通常有最长的运行时间,或者人们吹嘘他们的特定解决方案运行速度有多快。对于Python,有时方法有些笨拙——即向__main__添加计时代码。

描述Python程序运行时间的好方法是什么?


当前回答

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

描述性信息非常有用。它向您显示了该位的代码,当您处理内置库调用时,该代码会很有用。它告诉要查找代码的文件和行。

还想指出,OP说的是“剖析”,但似乎他是指“时机”。请记住,程序在评测时运行速度会变慢。

其他回答

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。

获取IPython笔记本上的快速配置文件统计信息。人们可以将line_profiler和memory_profile直接嵌入到笔记本中。

另一个有用的包是Pympler。它是一个强大的评测包,能够跟踪类、对象、函数、内存泄漏等。

了解了!

!pip install line_profiler
!pip install memory_profiler
!pip install pympler

加载它!

%load_ext line_profiler
%load_ext memory_profiler

使用它!


%时间

%time print('Outputs CPU time,Wall Clock time') 
#CPU times: user 2 µs, sys: 0 ns, total: 2 µs Wall time: 5.96 µs

给予:

CPU时间:CPU级执行时间systimes:系统级执行时间总计:CPU时间+系统时间墙上时间:墙上时钟时间


%计时

%timeit -r 7 -n 1000 print('Outputs execution time of the snippet') 
#1000 loops, best of 7: 7.46 ns per loop

给出给定循环次数(n)中的最佳运行次数(r)。输出系统缓存的详细信息:当代码片段被多次执行时,系统会缓存一些操作并不再执行,这可能会影响概要文件报告的准确性。


%普鲁士人

%prun -s cumulative 'Code to profile' 

给予:

函数调用数(ncall)每个函数调用有个条目(不同)每次呼叫所用时间(百分比)到函数调用为止的时间(cumtime)调用的函数/模块的名称等。。。


%记忆,记忆

%memit 'Code to profile'
#peak memory: 199.45 MiB, increment: 0.00 MiB

给予:

内存使用情况


%低压运行

#Example function
def fun():
  for i in range(10):
    print(i)

#Usage: %lprun <name_of_the_function> function
%lprun -f fun fun()

给予:

按行统计


系统大小

sys.getsizeof('code to profile')
# 64 bytes

返回对象的大小(以字节为单位)。


来自pympler的asizeof()

from pympler import asizeof
obj = [1,2,("hey","ha"),3]
print(asizeof.asizeof(obj,stats=4))

pympler.asizeof可用于调查某些Python对象消耗多少内存。与sys.getsizeof不同,asizeof递归地调整对象大小


来自pympler的跟踪器

from pympler import tracker
tr = tracker.SummaryTracker()
def fun():
  li = [1,2,3]
  di = {"ha":"haha","duh":"Umm"}
fun()
tr.print_diff()

跟踪函数的生存期。

Pympler包包含大量用于评测代码的高实用函数。这里无法涵盖所有这些。有关详细的概要文件实现,请参阅随附的文档。

Pympler文档

根据乔·肖(Joe Shaw)关于多线程代码无法按预期工作的回答,我认为cProfile中的runcall方法只是围绕着已配置的函数调用执行self.enable()和self.disable()调用,因此您可以简单地自己执行,并在对现有代码的干扰最小的情况下使用任何代码。

我发现,如果您不想使用命令行选项,该功能快速且易于使用。

要使用,只需在要分析的每个函数上方添加@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)

我发现cprofiler和其他资源更多地用于优化目的,而不是调试。

我制作了自己的测试模块,用于简单的python脚本速度测试。(在我的例子中,使用ScriptProfilerPy测试了1K+行py文件,并在几分钟内将代码速度提高了10倍。

模块ScriptProfilerPy()将运行代码,并向其添加时间戳。我把模块放在这里:https://github.com/Lucas-BLP/ScriptProfilerPy

Use:

from speed_testpy import ScriptProfilerPy

ScriptProfilerPy("path_to_your_script_to_test.py").Profiler()

输出: