cProfile和profile之间的区别-为什么没有profile.Profile.enable()方法?

时间:2018-10-25 22:26:14

标签: python

在Python 2.7和Python 3.6中,我都认为这可行:

from cProfile import Profile; p = Profile(); p.enable()

...这引发了异常:

from profile import Profile; p = Profile(); p.enable()

# -->  AttributeError: 'Profile' object has no attribute 'enable'

这让我感到惊讶,因为我认为(以及Python 3Python 2的官方文档)这两个模块应该提供相同的程序员接口:

  

profilecProfile模块均提供以下功能:

     

...

     

class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)

     

...

     

enable()

     

开始收集配置文件数据。

我想念什么? 使用profile.Profile()实例的正确方法是什么?

1 个答案:

答案 0 :(得分:1)

Python 3.8 和更新的文档中,他们已修复此问题以指定 enable()disable() 仅适用于 cProfile。对于 profile.Profile,您需要使用 run() 调用,而不是 enable()disable()。以下是每个示例:

cProfile.Profile:

# cProfile.Profile -- control with enable() and disable()
from datetime import datetime
import cProfile, pstats, io
sortby = 'cumulative'
pr1 = cProfile.Profile(lambda: int(datetime.now().timestamp()*1000000), 0.000001)
pr1.enable()
list(x for x in range(int(10*10*10*10/10*10+10)))
pr1.disable()
s1 = io.StringIO()
ps1 = pstats.Stats(pr1, stream=s1).sort_stats(sortby)
ps1.print_stats()
print(s1.getvalue())

profile.profile:

# profile.Profile -- control with run()
from datetime import datetime
import profile, pstats, io
sortby = 'cumulative'
pr2 = profile.Profile(datetime.now().timestamp, 0.000001)
pr2.run('list(x for x in range(int(10*10*10*10/10*10+10)))')
s2 = io.StringIO()
ps2 = pstats.Stats(pr2, stream=s2).sort_stats(sortby)
ps2.print_stats()
print(s2.getvalue())

以下是我观察到的两种形式之间的一些行为差异:

  • cProfile 会将分析函数的输出推送到 stdout,而 profile 不会。

  • 最终报告的 filename:lineno(function) 列的输出略有不同。 cProfile 通常会更易读地显示内置函数。

    • 然而,在我在这里提供的示例中,传入的函数实际上在 profile 的输出中是可识别的,而在 cProfile 的输出中 不是 -- 但如果使用一个命名函数,它在两个分析器的输出中都是可识别的(尽管传递给函数的参数只能从profile的输出中看到)。< /li>
  • 通过 cProfileprofile 运行相同的函数会提供略有不同的结果。 cProfile 似乎通常会减少大约两个函数调用,并且将为同一函数提供更有效的时间。

  • 使用分析器的可选自定义 timertimeunit 参数可以提供获得 微秒 精度与默认毫秒相比的优势精确。然而,尝试使用这些似乎有一些警告:

    • cProfile 的 timeunit 函数需要一个整数,而 profile 需要一个浮点数。这意味着您不能直接将 datetime.now().timestamp()cProfile 一起使用,而必须先将其包装在 lambda 中以将其转换为完整整数。
    • 此外,由于某种原因,profile 似乎无法与 datetime.now().timestamp() 一起正常工作,即使它是它所期望的格式。它将打印出负时间值——我在下面展示了这一点。切换到 lambda 形式以将其转换为整数无济于事,因为 profile 需要浮点数。
  • 以下是 profile.Profile 的输出,带有 datetime.now().timestamp() 计时器用于微秒供应。

     print(s2.getvalue())
           10015 function calls in -0.020 seconds`
    
     Ordered by: cumulative time
    
     ncalls  tottime  percall  cumtime  percall filename:lineno(function)
          0    0.000             0.000          profile:0(profiler)
          1    0.000    0.000    0.000    0.000 :0(setprofile)
      10011   -0.010   -0.000   -0.010   -0.000 <string>:1(<genexpr>)
          1   -0.010   -0.010   -0.020   -0.020 <string>:1(<module>)
          1   -0.000   -0.000   -0.020   -0.020 :0(exec)
          1   -0.000   -0.000   -0.020   -0.020 profile:0(list(x for x in range(int(10*10*10*10/10*10+10))))
    
  • 然而,这个问题可以通过提供一个自定义的计时器函数来解决(它甚至可以环绕 datetime.now() 以提供与 datetime.now().timestamp() 完全相同的输出格式),因为我不能'不解释。

例如如果我们定义:

def timenow():
   now = datetime.now()
   return (((now.year-1970) * 31557600) + (now.month * 2629800) + 
           (now.day * 86400) + (now.hour * 3600) + (now.minute * 60) +
           now.second + (now.microsecond * 0.000001))

我们现在可以使用 pr2 = profile.Profile(timenow, 0.000001),这将输出以下(正常的)报告:

>>> print(s2.getvalue())
         10015 function calls in 0.041 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.006    0.006    0.041    0.041 profile:0(list(x for x in range(int(10*10*10*10/10*10+10))))
        1    0.000    0.000    0.035    0.035 :0(exec)
        1    0.017    0.017    0.035    0.035 <string>:1(<module>)
    10011    0.018    0.000    0.018    0.000 <string>:1(<genexpr>)
        1    0.001    0.001    0.001    0.001 :0(setprofile)
        0    0.000             0.000          profile:0(profiler)

cProfile 的输出(使用上面定义的自定义 lambda 计时器)相比,我们有:

>>> print(s1.getvalue())
         10013 function calls in 0.031 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10011    0.031    0.000    0.031    0.000 <stdin>:1(<genexpr>)
        1    0.000    0.000    0.000    0.000 <stdin>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

至于为什么我们的自定义计时器函数给出了合理的结果而 datetime.now().timestamp()profile 没有,首先必须注意的是 timenow 没有为 datetime.now().timestamp() 提供计时.尽管它提供了接近纪元的时间,但它的偏差很小(不会影响正常的配置文件运行,因为增量在同一系统上是相同的)。 timenow 中使用的手动转换也肯定不如 datetime.now().timestamp() 中的高效。我只能推测后者效率太高,结果 profile 源代码无法正确测量。

TL;DR

除非有特殊原因,否则请使用 cProfile。官方 Python 文档还指出推荐的模块是 cProfile。虽然没有提供具体的理由,但文档似乎暗示 cProfile 可能表现得更好,适合大多数用户的目的(尽管它的语法稍微冗长一些)。