Python Profilers

源代码: Lib / profile.py和Lib / pstats.py


剖析器介绍

cProfileprofile提供deterministic profilingPython程序。一个 profile是一组统计信息,描述了程序的各个部分的执行时间和执行时间。这些统计数据可以通过pstats模块

Python标准库提供了相同配置接口的两种不同实现:

  1. cProfile建议大多数用户使用;它是一个具有合理开销的C扩展,使其适用于分析长时间运行的程序。基于lsprof,由Brett Rosen和TedCzotter撰写.
  2. profile,一个纯粹的Python模块,其界面被cProfile,但这会给配置文件程序带来很大的开销。如果你试图以某种方式扩展分析器,那么这个模块的任务可能会更容易。最初由Jim Roskind设计和编写.

注意

分析器模块旨在为给定程序提供执行配置文件,而不是用于基准测试目的(为此,有timeit以获得非常准确的结果)。这特别适用于针对C代码的benchmarkingPython代码:分析器引入了Python代码的开销,但不会引入C级函数,因此C代码看起来比anyPython更快.

即时用户手册

本节是为“不想阅读本手册”的用户提供的。它提供了一个非常简短的概述,并允许用户在现有应用程序中快速执行分析.

配置一个函数只需要一个参数,你可以这样做:

import cProfileimport recProfile.run("re.compile("foo|bar")")

(使用profile代替cProfile如果后者在你的系统上不可用。)

上面的动作将运行re.compile()并打印配置文件结果如下:

      197 function calls (192 primitive calls) in 0.002 secondsOrdered by: standard namencalls  tottime  percall  cumtime  percall filename:lineno(function)     1    0.000    0.000    0.001    0.001 <string>:1(<module>)     1    0.000    0.000    0.001    0.001 re.py:212(compile)     1    0.000    0.000    0.001    0.001 re.py:268(_compile)     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)

第一行表示监控了197个呼叫。在这些调用中,192是primitive,这意味着调用不是通过递归引起的。下一行:Ordered by: standard name,表示右侧列中的文本字符串用于对输出进行排序。列标题包括:

ncalls
表示调用次数
tottime
表示在给定函数中花费的总时间(不包括时间)对子功能的调整)
percall
tottime的商除以ncalls
cumtime
是累计花费的时间在这个和所有子功能中(来自invocationtill退出)。对于递归函数,这个数字是even准确的
//call
cumtime除以原始调用的商数
文件名:lineno(函数)
提供每个函数的相应数据

当第一列中有两个数字(例如3/1)时,表示该函数被递归。第二个值是原始调用的数量,前者是调用的总数。请注意,当函数没有递归时,这两个值是相同的,只有单个图形被打印.

不是在配置文件运行结束时打印输出,而是通过为run()函数指定文件名来保存结果:

import cProfileimport recProfile.run("re.compile("foo|bar")", "restats")

pstats.Statsclass以各种方式从文件和格式中读取配置文件结果.

文件cProfile也可以作为脚本来调用anotherscript。例如:

python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)

-o配置文件结果写入文件而不是stdout

-s指定其中一个sort_stats()排序值来排序输出。这仅适用于-o未提供的情况.

-m指定正在分析模块而不是脚本.

新版本3.7:添加了-m选项

// pstats模块Statsclass有多种方法可以操作和打印保存到配置文件结果文件中的数据:

import pstatsfrom pstats import SortKeyp = pstats.Stats("restats")p.strip_dirs().sort_stats(-1).print_stats()

strip_dirs()方法从所有模块名称中删除了无关的路径。sort_stats()方法根据打印的标准模块/行/名称字符串对所有条目进行排序。print_stats()方法打印出所有的统计数据。您可以尝试以下排序调用:

p.sort_stats(SortKey.NAME)p.print_stats()

第一个调用实际上将按功能名称对列表进行排序,第二个调用将打印出统计信息。以下是一些有趣的实验调用:

p.sort_stats(SortKey.CUMULATIVE).print_stats(10)

这将按函数中的累积时间对配置文件进行排序,然后仅打印十个最重要的行。如果你想了解算法的时间,你可以使用上面的行.

如果你想看看哪些函数循环很多,并花了很多时间,你会这样做:

p.sort_stats(SortKey.TIME).print_stats(10)

根据每个函数花费的时间进行排序,然后打印前十个函数的统计信息.

您也可以尝试:

p.sort_stats(SortKey.FILENAME).print_stats("__init__")

这将按文件名对所有统计信息进行排序,然后只打印类init方法的统计数据(因为它们拼写为__init__ inthem)。作为最后一个例子,你可以尝试:

p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, "init")

此行使用主键时间和累计时间的辅助键对统计信息进行排序,然后打印出一些统计信息。具体来说,该列表首先被淘汰到其原始大小的50%(re:.5),然后只保留包含init的行,并打印该子子列表.

如果你想知道什么函数调用上面的函数,你现在可以(p仍按照最后的标准排序)do:

p.print_callers(.5, "init")

你会得到一个调用者列表对于每个列出的函数

如果你想要更多的功能,你将不得不阅读手册,或者说以下函数的作用:

p.print_callees()p.add("restats")

作为脚本调用,pstats module是一个统计浏览器,用于读取和检查配置文件转储。它有一个简单的面向行的界面(使用cmd实现)和交互式帮助.

profilecProfile模块参考

两个profilecProfile模块提供以下功能:

profile.runcommand, filename=None, sort=-1

这个函数接受一个可以传递给exec()功能和可选的文件名。在所有情况下,此例程执行:

exec(command, __main__.__dict__, __main__.__dict__)

并从执行中收集分析统计信息。如果没有文件名,则此功能自动创建一个Stats实例并打印一个简单的分析报告。如果指定了排序值,则将其传递给Stats实例来控制结果的分类方式.

profile.runctxcommand, globals, locals, filename=None, sort=-1

此功能类似于run(),增加了为 @ 提供全球和当地人字典的论据command串。这个例程执行:

exec(command, globals, locals)

并收集run()功能上面

class profile.Profiletimer=None, timeunit=0.0, subcalls=True, builtins=True

这个类通常只在需要比cProfile.run()函数提供的更精确的分析控制时才使用.

可以提供自定义计时器来测量运行代码所需的时间timer论点。这必须是一个返回表示当前时间的单个数字的函数。如果数字是整数,则timeunit指定一个乘数,指定每个时间单位的持续时间。例如,如果计时器返回以千秒为单位测量的时间,则时间单位为.001.

直接使用Profile类允许格式化配置文件结果而不将配置文件数据写入文件:

import cProfile, pstats, iofrom pstats import SortKeypr = cProfile.Profile()pr.enable()# ... do something ...pr.disable()s = io.StringIO()sortby = SortKey.CUMULATIVEps = pstats.Stats(pr, stream=s).sort_stats(sortby)ps.print_stats()print(s.getvalue())
enable

开始收集剖析数据.

disable)

停止收集剖析数据.

create_stats

停止收集分析数据并在当前的配置文件内部记录结果.

print_stats (sort=-1)

创建一个Stats基于当前配置文件的对象并将结果打印到stdout.

dump_stats(filename)

将当前配置文件的结果写成filename.

runcmd

通过exec().

runctxcmd, globals, locals)描述cmd

通过exec()描述cmd具有指定的全球和本地环境.

runcall (func, *args, **kwargs )

Profile func(*args, **kwargs)

请注意,仅当被调用的命令/函数实际返回时,分析才有效。口译员被终止(例如通过sys.exit()调用被调用的命令/函数执行)不打印任何分析结果.

Stats

使用Stats类分析分析器数据.

class pstats.Stats(*filenames or profile, stream=sys.stdout)

此类构造函数从filename(或文件名列表)或Profile实例创建“统计对象”的实例。输出将打印到stream.

上述构造函数选择的文件必须由profilecProfile的相应版本创建。具体来说,有no与此配置程序的未来版本保证文件兼容性,并且与其他配置程序生成的文件不兼容,或者在不同的操作系统上运行相同的配置文件。如果提供了几个文件,则将合并相同功能的所有统计信息,以便可以在单个报告中考虑多个进程的整体视图。如果需要将其他文件与现有的Stats对象中的数据组合,可以使用add()方法.

而不是从文件中读取配置文件数据,cProfile.Profileprofile.Profile对象可以用作配置文件数据源.

Stats对象有以下方法:

strip_dirs

这个方法为Statsclass从文件名中删除所有前导路径信息。它在减小打印输出的尺寸以适应(接近)80列时非常有用。此方法修改了对象,并且删除了剥离的信息。执行astrip操作后,该对象被认为是以“随机”顺序排列的,就像在对象初始化和加载之后一样。如果strip_dirs()导致两个函数名可以区分(它们在同一文件名的同一行,并且具有相同的函数名),然后这两个条目的统计信息累积到一个条目中

add*filenames

Statsclass将其他分析信息累积到当前分析对象中。它的参数应该是由相应版本的profile.run()cProfile.run()。同名(re:file,line,name)函数的统计信息自动累积到单个函数统计中.

dump_statsfilename

保存加载到Stats对象到名为filename的文件。如果文件不存在,则创建该文件,如果该文件已存在则被覆盖。这相当于profile.ProfilecProfile.Profile classes

sort_stats*keys

此方法通过根据提供的标准对Stats对象进行排序来修改它。参数可以是字符串或SortKeyenum,用于标识排序的基础(例如:"time", "name",SortKey.TIME要么 SortKey.NAME)。SortKey枚举参数对字符串参数有优势,因为它更健壮,更容易出错.

当提供多个密钥时,如果在它们之前选择的所有密钥中存在相等的情况,则使用附加密钥作为辅助标准。例如, sort_stats(SortKey.NAME, SortKey.FILE)将根据函数名称对条目进行排序,并按文件名排序解析所有联系(相同的函数名称).

对于字符串参数,缩写可以用于任何键名,只要缩写是明确的.

以下是有效字符串和SortKey:

有效字符串Arg 有效枚举Arg 含义
"calls" SortKey.CALLS 调用count
"cumulative" SortKey.CUMULATIVE 累积时间
"cumtime" N / A 累积时间
"file" N / A 文件名
"filename" SortKey.FILENAME 文件名
"module" N / A 文件名
"ncalls" N / A call count
"pcalls" SortKey.PCALLS 原始呼叫计数
"line" SortKey.LINE 行号
"name" SortKey.NAME 功能名称
"nfl" SortKey.NFL name/file/line
"stdname" SortKey.STDNAME 标准名称
"time" SortKey。TIME 内部时间
"tottime" N / A 内部时间

请注意,统计数据上的所有排序都是降序排列(首先放置大部分时间消耗的项目),名称,文件和行号搜索按升序排列(按字母顺序排列)。SortKey.NFLSortKey.STDNAME是标准名称是打印名称的asort,这意味着嵌入的行数字以奇怪的方式进行比较。例如,第3,第20和第40行(如果文件名相同)将出现在字符串顺序20,3和40中。相反,SortKey.NFL对行号进行数字比较。事实上,sort_stats(SortKey.NFL)sort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE).

相同。出于向后兼容的原因,数字参数-1, 0,12被允许。他们被解释为"stdname","calls", "time",和"cumulative"分别。如果使用这种旧式格式(数字),将只使用一个排序键(数字键),并且将默默忽略其他参数.

新版本3.7:添加了SortKey枚举

reverse_order

这个方法为Statsclass反转对象内基本列表的顺序。请注意,默认情况下,根据选择的排序键正确选择升序和降序.

print_stats (*restrictions )

这个方法为Statsclass打印出profile.run()定义。

打印的顺序是基于最后的sort_stats()对物体进行的操作(主题tocaveats在add()strip_dirs()).

提供的参数(如果有)可用于将列表限制为显着的条目。最初,该列表被视为完整的配置文件功能集。每个限制都是一个整数(用于选择行的数量),或者介于0.0和1.0之间的小数(包括选择一行的百分比),或者一个将被解释为常规表达式的字符串(以模式匹配打印的标准名称)如果提供了几个限制,那么它们将按顺序应用。例如:

print_stats(.1, "foo:")

首先将打印限制在列表的前10%,然后只打印属于文件名.*foo:的打印功能。相反,命令:

print_stats("foo:", .1)

将列表限制为所有具有文件名.*foo:的函数,然后继续只打印前10%的函数.

print_callers (*restrictions)

这个方法为Statsclass打印在配置文件数据库中调用每个函数的所有函数的列表。顺序与print_stats(),限制论证的定义也是一样的。每个呼叫者都在自己的线路上报告。格式略有不同,具体取决于产生统计数据的发起人:

  • profile在每个callerto显示此特定呼叫的次数后,括号中会显示一个数字。为方便起见,第二个非括号内的数字重复了右侧功能所累积的时间.
  • cProfile,每个调用者前面都有三个数字:这个特定调用的次数,以及当前函数在该特定调用者调用时所花费的总时间和累计时间.
print_callees*restrictions

这个方法适用于Statsclass打印由指定函数调用的所有函数的列表。除了调用方向的反转之外(re:调用vs被调用),参数和顺序与print_callers()方法相同.

确定性分析是什么?

Deterministic profiling是意味着反映所有functioncall, function returnexception事件都被监视的事实,并且对这些事件之间的间隔进行了精确定位(在此期间用户的代码正在执行)。相反,statistical profiling(这不是由这个模块完成)随机采样有效指令指针,并且在时间花费的地方。后一种技术传统上没有任何开销(因为代码不需要检测),但只提供了时间花费的相关指示.

在Python中,由于在执行期间有一个解释器处于活动状态,因此检测的存在代码不需要进行确定性分析。Pythonautomatically提供了hook(每个事件的可选回调)。此外,Python的解释性质往往会增加执行的开销,确定性分析往往只会在典型应用程序中添加小的处理开销。结果是确定性分析并不昂贵,但提供了关于Python程序执行的大量运行时统计信息.

呼叫计数统计信息可用于识别代码中的错误(令人惊讶的计数),并识别可能的内联- 扩展点(高呼叫计数)。内部时间统计信息可用于识别应该仔细优化的“热循环”。应使用累积时间统计来识别算法选择中的高级别。请注意,此探查器中累积时间的异常处理允许将算法的递归实现的统计数据直接与迭代实现进行比较.

Limitations

一个限制与定时信息的准确性有关。涉及准确性的确定性分析器存在一些基本问题。最明显的限制是潜在的“时钟”仅以大约0.001秒的速率(通常)滴答。因此,没有测量结果比基础时钟更准确。如果进行了足够的测量,那么“误差”将倾向于平均。不幸的是,删除第一个错误会导致第二个错误来源.

第二个问题是,在发送事件时“需要一段时间”,直到探查器的调用才真正得到时间gets时钟的状态。类似地,从获取时钟值(然后是squirreledaway)的时间退出探查器事件处理程序时会有一定的延迟,直到用户的代码再次执行。因此,多次调用的函数或调用许多函数通常会累积此错误。以这种方式累积的误差通常小于时钟精度(小于一个时钟周期),但它can积累并变得非常重要.

问题更重要profile而不是低头cProfile。为此原因, profile提供了一种为给定平台自我校准的方法,以便可以概率地(平均)删除此错误。校对器校准后,它将更准确(至少在正方形意义上),但它有时会产生负数(当呼叫计数特别低时,概率之神对你不利:-)。)做not被剖析器中的负数警告。如果校准过你的探测器,他们应该only出现,结果实际上比没有校准好.

校准

的剖析器profile模块从每个事件处理时间中减去一个常量,以补偿调用时间函数的开销,并消除结果。默认情况下,常量为0.以下过程可用于为给定平台获取更好的常量(请参阅限制).

import profilepr = profile.Profile()for i in range(5):    print(pr.calibrate(10000))

该方法直接执行参数给出的Python调用次数,再次在分析器下执行,测量两者的时间。然后,它计算每个探查器事件的隐藏开销,并将其作为浮点数返回。例如,在运行Mac OS X的1.8Ghz Intel Core i5上,使用Python的time.process_time()作为计时器,神奇的数字约为4.04e-6.

本练习的目的是获得相当一致的结果。如果您的电脑是非常快,或你的计时器功能分辨率很差,你可能要通过100000甚至1000000来获得一致的结果.

当你得到一致的答案时,有三种方法可以使用它:

import profile# 1. Apply computed bias to all Profile instances created hereafter.profile.Profile.bias = your_computed_bias# 2. Apply computed bias to a specific Profile instance.pr = profile.Profile()pr.bias = your_computed_bias# 3. Specify computed bias in instance constructor.pr = profile.Profile(bias=your_computed_bias)

如果你有一个选择,你最好选择一个较小的常数,然后你的结果将“不经常”显示为配置文件统计中的负数.

使用自定义计时器

如果要更改当前时间的确定方式(例如,强制使用挂钟时间或经过的处理时间),请将所需的计时功能传递给Profile类构造函数:

pr = profile.Profile(your_time_func)

然后,生成的探查器将调用your_time_func。根据你是否使用profile.ProfilecProfile.Profile,your_time_func的返回值将被不同地解释:

profile.Profile

your_time_func应该返回一个数字,或者一个数字列表是当前时间(像什么 os.times()回报)。如果函数返回一个时间数,或返回的数字列表长度为2,那么你将得到一个特别快的版本的dispatchroutine。

警告你应该为你选择的计时器功能校准探查器类(见校准)。对于大多数机器而言,返回单个整数值的时间将在分析期间的低开销方面提供最佳结果。(os.times()pretty坏,因为它返回浮点值的元组)。如果你想以最干净的方式替换abetter计时器,派生一个类和hardwire位置调度方法,最好处理你的计时器调用,以及适当的校准常数.

cProfile.Profile

your_time_func应该返回一个数字。如果它返回整数,您还可以使用第二个参数调用类构造函数,该参数指定一个单位时间的实际持续时间。例如,如果your_integer_time_func返回以千秒为单位测量的次数,则构建Profile实例如下:

pr = cProfile.Profile(your_integer_time_func, 0.001)

作为cProfile.Profile无法校准等级,应谨慎使用定制定时器功能,并应尽可能快。使用自定义计时器可获得最佳效果,可能需要在内部的C源代码中对其进行硬编码_lsprof模块

Python 3.3在time中添加了几个新功能,可用于精确测量过程或挂钟时间。例如,请参阅time.perf_counter().