首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

The Python Profilers

1.介绍分析器

cProfileprofile提供Python程序的确定性分析。一个配置文件是一组统计数据,描述如何经常和执行的程序多久各个部分。这些统计数据可以通过pstats模块格式化成报告。

Python标准库提供了相同概要分析界面的三种不同实现:

  1. cProfile建议大多数用户使用; 这是一个C扩展,具有合理的开销,使其适合分析长时间运行的程序。基于lsprofBrett Rosen和Ted Czotter的贡献。2.5版本中的新功能。

2. profile,它是一个纯粹的Python模块,其接口被模仿cProfile,但是它为剖析程序增加了大量开销。如果您试图以某种方式扩展探查器,则使用此模块可能会更轻松。最初由Jim Roskind设计和撰写。

在版本2.4中进行了更改:现在还报告了调用内置函数和方法所花费的时间。

3. hotshot是一个实验性的C模块,其重点是最大限度地减少分析开销,代价是数据后处理时间更长。它不再被维护,并可能在未来的Python版本中被丢弃。 在版本2.5中进行了更改:结果应该比过去更有意义:时序核心包含一个严重错误。

profilecProfile模块具有相同的接口,因此他们大多是互换; cProfile具有低得多的开销但更新,并且可能在所有系统上都不可用。 cProfile实际上是内部_lsprof模块之上的兼容层 。该hotshot模块专供特殊用途使用。

注意

分析器模块旨在为给定程序提供执行配置文件,而不是用于基准测试(为此,有timeit相当准确的结果)。这特别适用于将Python代码与C代码进行基准比较:分析器为Python代码引入了开销,但不会为C级函数开销,因此C代码看起来比任何Python代码都要快。

即时用户手册

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

要分析一个只有一个参数的函数,你可以这样做:

代码语言:javascript
复制
import cProfile
import re
cProfile.run('re.compile("foo|bar")')

(使用profile而不是cProfile如果后者无法使用您的系统上。)

上述操作将运行re.compile()并打印如下所示的配置文件结果:

代码语言:javascript
复制
      197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  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是原始的,这意味着调用不是通过递归来引发的。下一行:表示最右列中的文本字符串用于对输出进行排序。列标题包括:Ordered by: standard name

ncalls

对于调用的数量,

tottime

在给定函数中花费的总时间(并且不包括调用子函数的时间)

percall

tottime除以ncalls的商

cumtime

是在这个和所有子函数(从调用到退出)中累积的时间。即使是递归函数,这个数字也是准确的。

percall

cumtime除以原始调用的商

filename:lineno(function)

提供每个函数的相应数据

如果第一列中有两个数字(例如3/1),则意味着该函数被递归。第二个值是原始呼叫的数量,前者是呼叫的总数。请注意,当函数不递归时,这两个值是相同的,并且只打印单个数字。

除了在配置文件运行结束时打印输出,您可以通过为run()函数指定文件名将结果保存到文件中:

代码语言:javascript
复制
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')

pstats.Stats班从文件中读取配置文件的结果,并格式化它们以不同的方式。

该文件cProfile也可以作为脚本来调用另一个脚本。例如:

代码语言:javascript
复制
python -m cProfile [-o output_file] [-s sort_order] myscript.py

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

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

pstats模块的Stats类具有多种方法来操作和打印保存到配置文件结果文件中的数据:

代码语言:javascript
复制
import pstats
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()

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

代码语言:javascript
复制
p.sort_stats('name')
p.print_stats()

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

代码语言:javascript
复制
p.sort_stats('cumulative').print_stats(10)

这会按照函数中的累积时间排序配置文件,然后仅打印十条最重要的行。如果您想了解哪些算法需要花费时间,那么您将使用上述行。

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

代码语言:javascript
复制
p.sort_stats('time').print_stats(10)

根据每个功能花费的时间进行排序,然后打印前十个功能的统计数据。

你也可以尝试:

代码语言:javascript
复制
p.sort_stats('file').print_stats('__init__')

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

代码语言:javascript
复制
p.sort_stats('time', 'cum').print_stats(.5, 'init')

该行使用时间主键和累积时间的辅助键对统计信息进行排序,然后打印出一些统计信息。具体而言,列表首先被降至.5其原始大小的50%(re:),然后仅init保留行,并打印该子子列表。

如果您想知道哪些函数称为上述函数,您现在可以(p 仍然按照最后一个标准排序):

代码语言:javascript
复制
p.print_callers(.5, 'init')

并且您将获得每个列出功能的呼叫者列表。

如果你需要更多的功能,你将不得不阅读手册,或猜测以下功能的作用:

代码语言:javascript
复制
p.print_callees()
p.add('restats')

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

profilecProfile模块参考

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

profile.runcommandfilename = Nonesort = -1

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

代码语言:javascript
复制
exec(command, __main__.__dict__, __main__.__dict__)

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

profile.runctxcommandglobalslocalsfilename = None

该函数类似于run()使用添加的参数为命令字符串提供全局变量和局部变量字典。该例程执行:

代码语言:javascript
复制
exec(command, globals, locals)

并收集上述run()功能中的分析统计信息。

classprofile.Profiletimer = Nonetimeunit = 0.0subcalls = Truebuiltins= True

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

自定义计时器可用于测量通过timer参数运行代码需要多长时间。这必须是一个返回表示当前时间的单个数字的函数。如果数字是一个整数,那么timeunit 指定一个乘数,它指定每个时间单位的持续时间。例如,如果计时器返回以数千秒计量的时间,则时间单位将是.001

直接使用Profile该类可以在不将配置文件数据写入文件的情况下格式化配置文件结果:

代码语言:javascript
复制
import cProfile, pstats, StringIO
pr = cProfile.Profile()
pr.enable()
# ... do something ...
pr.disable()
s = StringIO.StringIO()
sortby = 'cumulative'
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print s.getvalue()

enable()

开始收集分析数据。

disable()

停止收集分析数据。

create_stats()

停止收集性能分析数据并将内部结果记录为当前配置文件。

print_statssort = -1

Stats根据当前配置文件创建一个对象并将结果输出到stdout。

dump_stats文件名

将当前配置文件的结果写入文件

runcmd

通过分析cmd exec()

runctxcmdglobalslocals

通过exec()指定的全局和本地环境对cmd进行分析。

runcallfunc* args** kwargs

轮廓 func(*args, **kwargs)

Stats

分析器数据分析使用Stats该类进行。

classpstats.Stats* filenames或profilestream = sys.stdout

此类构造函数根据文件名(或文件名列表)或实例创建“统计对象” Profile实例。输出将打印到由流指定的

由上述构造函数选择的文件必须由相应版本的profileor 创建cProfile。具体而言,该分析器的未来版本没有文件兼容性保证,并且与其他分析器生成的文件或同一分析器在不同操作系统上运行的文件不兼容。如果提供了多个文件,则所有相同功能的统计数据将被合并,以便可以在单个报告中考虑多个进程的总体视图。如果需要将其他文件与现有Stats对象中的数据结合使用,则add()可以使用该方法。

可以将一个cProfile.Profile 或一个profile.Profile对象用作配置文件数据源,而不是从文件读取配置文件数据。

Stats 对象有以下方法:

strip_dirs()

Stats该类的此方法从文件名中删除所有前导路径信息。它在缩小打印输出尺寸以适应(接近)80列时非常有用。此方法修改对象,并且剥离的信息丢失。执行剥离操作后,该对象被视为以“随机”顺序输入条目,因为它刚好在对象初始化和加载之后。如果strip_dirs()导致两个函数名无法区分(它们位于同一文件名的同一行,并具有相同的函数名),那么这两个条目的统计信息将累积到一个条目中。

add*文件名

Stats该类的此方法将其他分析信息累加到当前分析对象中。它的参数应该引用由相应版本的profile.run() or 创建的文件名cProfile.run()。同名(re:文件,行,名称)函数的统计信息会自动累积到单个函数统计信息中。

dump_stats文件名

将加载到Stats对象中的数据保存到名为 filename的文件中。如果该文件不存在,则会创建该文件,如果该文件已存在,则会覆盖该文件。这相当于profile.ProfilecProfile.Profile类上的同名方法。

2.3版本的新功能。

sort_stats*键

该方法Stats通过根据提供的标准对对象进行排序来修改对象。参数通常是标识排序基础的字符串(例如:'time''name')。

当提供多于一个密钥时,当在它们之前选择的所有密钥相等时,附加密钥用作次要标准。例如,将根据其函数名称对所有条目进行排序,并通过按文件名排序来解析所有关系(相同的函数名称)。sort_stats('name', 'file')

缩写可以用于任何键名,只要缩写是明确的。以下是目前定义的关键字:

有效的Arg

含义

'calls'

调用计数

'cumulative'

累计时间

'cumtime'

累计时间

'file'

文件名

'filename'

文件名

'module'

文件名

'ncalls'

通话计数

'pcalls'

原始调用计数

'line'

'name'

函数名称

'nfl'

名/文件/行

'stdname'

标准名称

'time'

内部时间

'tottime'

内部时间

请注意,统计信息的所有排序均按降序排列(首先放置耗时最多的项目),其中名称,文件和行号搜索按升序排列(按字母顺序排列)。之间的微妙的区别 'nfl''stdname'是标准名称是一种名称为打印,这意味着该嵌入行号获取一种奇怪的方式进行比较。例如,第3行,第20行和第40行(如果文件名相同)将以字符串顺序20,3和40出现。相反, 'nfl'对行号进行数字比较。其实 sort_stats('nfl')也是一样的。sort_stats('name', 'file', 'line')

对于向后兼容性的原因,数值参数-101,和2被允许的。它们被解释为'stdname''calls''time',和'cumulative'分别。如果使用这种旧样式格式(数字),则只会使用一个排序键(数字键),而其他参数将被忽略。

reverse_order()

这个Stats类的方法颠倒了对象内基本列表的排序。请注意,默认情况下,升序和降序的选择是基于所选择的排序键进行的。

print_stats*限制

这个Stats类的方法按照profile.run()定义中的描述打印出一个报告。

打印顺序sort_stats()取决于在对象上执行的最后一个 操作(需要注意add()和 注意strip_dirs())。

提供的参数(如果有的话)可用于将列表限制为重要条目。最初,该列表被视为完整的配置功能集。每个限制条件可以是整数(用于选择行数)或0.0和1.0之间的小数部分(用于选择行的百分比),也可以是正则表达式(用于匹配打印的标准名称)提供限制,然后按顺序应用,例如:

代码语言:javascript
复制
print_stats(.1, 'foo:')

首先将打印限制为列表的前10%,然后仅打印属于文件名的部分的功能.*foo:。相比之下,命令:

代码语言:javascript
复制
print_stats('foo:', .1)

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

print_callers*限制

这个Stats类的方法打印一个调用配置文件数据库中所有调用每个函数的函数的列表。排序与提供的顺序相同print_stats(),并且限制参数的定义也是相同的。每个呼叫者都在自己的线路上报告。格式根据生成统计信息的分析器略有不同:

  • 随着profile,数字显示在括号中每个调用者后,显示此特定调用了多少次提出。为方便起见,第二个非括号的数字重复在右侧的函数中花费的累积时间。
  • 同时cProfile,每个调用者前面都有三个数字:该特定调用的次数,以及该特定调用者调用当前函数所花费的总时间和累计时间。

print_callees(*restrictions)

这个Stats类的方法打印一个由指定函数调用的所有函数的列表。除了调用方向的这种逆转(re:称为vs被调用)之外,参数和顺序与print_callers()方法相同。

5.什么是确定性分析?

确定性分析旨在反映所有函数调用函数返回异常事件都受到监视,并且对这些事件之间的间隔(在此期间用户的代码正在执行)进行精确定时。相比之下,统计分析(这不是由该模块完成的)随机采样有效指令指针,并推断出在哪里花费时间。后一种技术传统上涉及较少的开销(因为代码不需要检测),但仅提供时间花费在何处的相对指示。

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

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

6.限制

一个限制与时间信息的准确度有关。确定性分析器涉及准确性存在根本问题。最明显的限制是底层的“时钟”仅以约0.001秒的速率(典型值)滴答。因此,没有测量结果比底层时钟更准确。如果进行了足够的测量,那么“错误”将趋于平均。不幸的是,消除第一个错误会导致第二个错误。

第二个问题是,从事件发布到分析器调用获取时间的“调用需要一段时间”实际上会获得时钟状态。类似地,从获取时钟值(然后松鼠消失)的时间退出事件探查器时,存在一定的滞后,直到用户的代码再次执行。因此,多次调用或调用多个函数的函数通常会累积该错误。以这种方式积累的误差通常小于时钟的精度(小于一个时钟刻度),但它可以累积并变得非常重要。

profile与低开销相比,问题更重要cProfile。由于这个原因,profile为给定的平台提供了一种校准方法,以便可以将这个错误概率地(平均地)移除。在分析器校准后,它会更准确(以最小二乘的方式),但它有时会产生负数(当通话计数特别低时,概率神对你起作用:-)。)不要通过配置文件中的负数惊慌。它们应该在您校准过轮廓仪时出现,并且结果实际上比没有校准时好。

7.校准

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

代码语言:javascript
复制
import profile
pr = profile.Profile()
for i in range(5):
    print pr.calibrate(10000)

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

这个练习的目的是得到一个相当一致的结果。如果您的计算机速度非常快,或者您的计时器功能的分辨率较差,则可能必须通过100000甚至1000000才能获得一致的结果。

当你有一个一致的答案时,有三种方法可以使用它:[1]

代码语言:javascript
复制
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)

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

8.使用自定义计时器

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

代码语言:javascript
复制
pr = profile.Profile(your_time_func)

然后生成的分析器将会调用your_time_func。根据您使用的profile.Profile还是cProfile.Profileyour_time_func返回值的解释不同:

profile.Profile

your_time_func应该返回一个单一的数字,或者是一个总数是当前时间的数字列表(比如os.times()返回的)。如果函数返回单个时间数字,或者返回的数字列表的长度为2,那么您将得到特别快速的派遣例程。

被警告您应该校准您选择的定时器功能的探查器类别(请参阅校准)。对于大多数机器来说,返回单个整数值的计时器将在分析过程中提供低成本方面的最佳结果。(os.times()非常糟糕的,因为它返回浮点值的元组)。如果您想以最简洁的方式替换更好的计时器,可以派生出一个班级并硬连线替代派遣方法,以最佳方式处理您的计时器呼叫以及适当的校准常数。

cProfile.Profile

your_time_func应该返回一个数字。如果它返回整数,也可以用第二个参数调用类构造函数,该参数指定一个单位时间的实际持续时间。例如,如果your_integer_time_func返回时间以千位秒为单位进行计算,则您将按Profile如下所示构造实例:

代码语言:javascript
复制
pr = cProfile.Profile(your_integer_time_func, 0.001)

由于cProfile.Profile班级无法校准,因此应小心使用自定义计时器功能,并且应尽可能快。为了使用定制定时器获得最佳结果,可能需要在内部_lsprof模块的C源代码中对其进行硬编码。

脚注

在Python 2.2之前,有必要编辑探查器源代码以将偏差作为文字编号嵌入。你仍然可以,但是这种方法不再被描述,因为不再需要。

扫码关注腾讯云开发者

领取腾讯云代金券