如何逐行分析 Python 代码?

我一直在使用 cProfile 来分析我的代码,它工作得很好。我还使用 GProf2dot.py来可视化结果(使其更清晰一些)。

然而,cProfile (以及我目前看到的大多数其他 Python 分析器)似乎只在函数调用级别进行分析。当从不同的地方调用某些函数时,这会导致混淆——我不知道调用 # 1或调用 # 2是否占用了大部分时间。当所讨论的函数有六层深,从其他七个地方调用时,情况就更糟了。

我怎么才能得到一条一条的侧写?

而不是这样:

function #12, total time: 2.0s

我想看看这样的东西:

function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s

CProfile 确实显示了总共有多少时间“传输”到父节点,但是当您有一大堆层和相互连接的调用时,这个连接又丢失了。

理想情况下,我希望有一个图形用户界面,可以解析数据,然后显示我的源文件,每一行的总时间。就像这样:

main.py:


a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s

然后,我就可以单击第二个“ func (c)”调用,查看该调用中与“ func (a)”调用不同的内容所占用的时间。这说得通吗?

105911 次浏览

我相信这就是 Robert Kern 的 line _ profiler的目的:

File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s


Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
149                                           @profile
150                                           def Proc2(IntParIO):
151     50000        82003      1.6     13.5      IntLoc = IntParIO + 10
152     50000        63162      1.3     10.4      while 1:
153     50000        69065      1.4     11.4          if Char1Glob == 'A':
154     50000        66354      1.3     10.9              IntLoc = IntLoc - 1
155     50000        67263      1.3     11.1              IntParIO = IntLoc - IntGlob
156     50000        65494      1.3     10.8              EnumLoc = Ident1
157     50000        68001      1.4     11.2          if EnumLoc == Ident1:
158     50000        63739      1.3     10.5              break
159     50000        61575      1.2     10.1      return IntParIO

你也可以使用 侧写(皮皮)。 如果要分析整个执行过程,则不需要修改源代码。 您还可以通过两种方式分析一个较大程序的子集:

  • 当到达代码中的特定点时切换分析,例如:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
    some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
    
  • toggle profiling asynchronously from call stack (requires a way to trigger this code in considered application, for example a signal handler or an available worker thread) by using statistical profiling:

    import pprofile
    profiler = pprofile.StatisticalProfile()
    statistical_profiler_thread = pprofile.StatisticalThread(
    profiler=profiler,
    )
    with statistical_profiler_thread:
    sleep(n)
    # Likewise, process profile content
    

Code annotation output format is much like line profiler:

$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #|      Hits|         Time| Time per hit|      %|Source code
------+----------+-------------+-------------+-------+-----------
1|         2|  3.21865e-05|  1.60933e-05|  0.00%|import threading
2|         1|  5.96046e-06|  5.96046e-06|  0.00%|import time
3|         0|            0|            0|  0.00%|
4|         2|   1.5974e-05|  7.98702e-06|  0.00%|def func():
5|         1|      1.00111|      1.00111| 99.54%|  time.sleep(1)
6|         0|            0|            0|  0.00%|
7|         2|  2.00272e-05|  1.00136e-05|  0.00%|def func2():
8|         1|  1.69277e-05|  1.69277e-05|  0.00%|  pass
9|         0|            0|            0|  0.00%|
10|         1|  1.81198e-05|  1.81198e-05|  0.00%|t1 = threading.Thread(target=func)
(call)|         1|  0.000610828|  0.000610828|  0.06%|# /usr/lib/python2.7/threading.py:436 __init__
11|         1|  1.52588e-05|  1.52588e-05|  0.00%|t2 = threading.Thread(target=func)
(call)|         1|  0.000438929|  0.000438929|  0.04%|# /usr/lib/python2.7/threading.py:436 __init__
12|         1|  4.79221e-05|  4.79221e-05|  0.00%|t1.start()
(call)|         1|  0.000843048|  0.000843048|  0.08%|# /usr/lib/python2.7/threading.py:485 start
13|         1|  6.48499e-05|  6.48499e-05|  0.01%|t2.start()
(call)|         1|   0.00115609|   0.00115609|  0.11%|# /usr/lib/python2.7/threading.py:485 start
14|         1|  0.000205994|  0.000205994|  0.02%|(func(), func2())
(call)|         1|      1.00112|      1.00112| 99.54%|# demo/threads.py:4 func
(call)|         1|  3.09944e-05|  3.09944e-05|  0.00%|# demo/threads.py:7 func2
15|         1|  7.62939e-05|  7.62939e-05|  0.01%|t1.join()
(call)|         1|  0.000423908|  0.000423908|  0.04%|# /usr/lib/python2.7/threading.py:653 join
16|         1|  5.26905e-05|  5.26905e-05|  0.01%|t2.join()
(call)|         1|  0.000320196|  0.000320196|  0.03%|# /usr/lib/python2.7/threading.py:653 join

请注意,由于 pprofile 不依赖于代码修改,因此它可以分析顶级模块语句,允许分析程序启动时间(导入模块、初始化全局变量需要多长时间,...)。

它可以生成缓存格式的输出,因此可以使用 克卡什茵轻松浏览大型结果。

披露: 我是个人简介的作者。

PyVmMonitor 有一个实时视图,可以在那里提供帮助(您可以连接到正在运行的程序并从中获得统计信息)。

见: http://www.pyvmmonitor.com/

你可以借助 Line _ profiler软件包的帮助

1.1安装软件包:

    pip install line_profiler

2. 使用魔法命令将软件包加载到 Python/笔记本环境中

    %load_ext line_profiler

3. 如果你想分析一个函数的代码,那么
按以下步骤行事:

    %lprun -f demo_func demo_func(arg1, arg2)

如果按照以下步骤,您将得到一个包含所有细节的格式化输出:)

Line #      Hits      Time    Per Hit   % Time  Line Contents
1                                           def demo_func(a,b):
2         1        248.0    248.0     64.8      print(a+b)
3         1         40.0     40.0     10.4      print(a)
4         1         94.0     94.0     24.5      print(a*b)
5         1          1.0      1.0      0.3      return a/b

只是为了提高@Joe Kington 的 上述答案

对于 Python 3. x,使用 Line _ profiler:


安装:

pip install line_profiler

用法:

假设您有一个程序 main.py,在程序内部有希望与时间相关的函数 fun_a()fun_b(); 您需要在函数定义之前使用修饰符 @profile。例如:

@profile
def fun_a():
#do something


@profile
def fun_b():
#do something more


if __name__ == '__main__':
fun_a()
fun_b()

程序可以通过执行 shell 命令进行配置:

$ kernprof -l -v main.py

可以使用 $ kernprof -h获取参数

Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...


Options:
--version             show program's version number and exit
-h, --help            show this help message and exit
-l, --line-by-line    Use the line-by-line profiler from the line_profiler
module instead of Profile. Implies --builtin.
-b, --builtin         Put 'profile' in the builtins. Use 'profile.enable()'
and 'profile.disable()' in your code to turn it on and
off, or '@profile' to decorate a single function, or
'with profile:' to profile a single section of code.
-o OUTFILE, --outfile=OUTFILE
Save stats to <outfile>
-s SETUP, --setup=SETUP
Code to execute before the code to profile
-v, --view            View the results of the profile in addition to saving
it.

结果将在控制台上打印为:

Total time: 17.6699 s
File: main.py
Function: fun_a at line 5


Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
5                                           @profile
6                                           def fun_a():
...



编辑: 分析器的结果可以使用 TAMPPA包进行解析。使用它,我们可以逐行获得所需的图像 plot