Post

Profile分析Python代码性能

介绍

几乎每种语言都有分析代码性能的工具,Python也不例外,其自带的profile模块,就提供了该功能。

用法

API方式

API方式允许我们以代码的方式来运行性能测试,API接口为:

1
2
import profile
profile.run(<function_name>, <file_name>, <sort>)

首先需要导入profile模块,然后调用run函数。若未指定文件名,则会将分析结果打印出来。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import profile

# 计算x1到x2的阶乘
def cal_factorial(x1, x2):
    r = 1
    for i in range(x1, x2 + 1):
        r *= i
    return r

def functionA():
    cal_factorial(1, 10000)
    
def functionB():
    cal_factorial(1, 100000)

def test_code():
    functionA()
    functionB()

# 打印结果,不排序
profile.run("test_code()")
# 打印结果,以累计时间排序
# profile.run("test_code()", None, "cumulative")
# 结果保存到文件:profile
# profile.run("test_code()", "profile")

命令行

相比于API方式,使用命令行则会更加方便的分析我们已经写好的代码。语法:

不保存文件:python -m cProfile <code_file_name>.py

保存分析结果到文件:python -m cProfile -o <save_file_name> <code_file_name>.py

解析保存的文件

在上一步中生成的文件并非文本文件,无法直接打开查看结果,需要先将文件解析出来。

解析需要用到pstats模块,一般直接在命令行中执行python脚本即可。

python -c "import pstats; p=pstats.Stats('<file_name>'); p.sort_stats('<sort>').print_stats()"

其中,sort_stats支持以下参数排序:

  • calls/ncalls:调用次数
  • cumtime/cumulative:累计时间
  • filename/module:文件名
  • line:行号
  • name:函数名
  • nfl:函数名,文件名,行号(name/file/line)
  • pcalls:原始调用次数
  • stdname:标准函数名
  • time/tottime:时间(不包括子函数时间)

参考:在pstats.py中找到如下定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
The sort_stats() method now processes some additional options (i.e., in
    addition to the old -1, 0, 1, or 2 that are respectively interpreted as
    'stdname', 'calls', 'time', and 'cumulative').  It takes either an
    arbitrary number of quoted strings or SortKey enum to select the sort
    order.

    For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
    SortKey.NAME) sorts on the major key of 'internal function time', and on
    the minor key of 'the name of the function'.  Look at the two tables in
    sort_stats() and get_sort_arg_defs(self) for more examples.

class SortKey(str, Enum):
    CALLS = 'calls', 'ncalls'
    CUMULATIVE = 'cumulative', 'cumtime'
    FILENAME = 'filename', 'module'
    LINE = 'line'
    NAME = 'name'
    NFL = 'nfl'
    PCALLS = 'pcalls'
    STDNAME = 'stdname'
    TIME = 'time', 'tottime'

sort_arg_dict_default = {
            "calls"     : (((1,-1),              ), "call count"),
            "ncalls"    : (((1,-1),              ), "call count"),
            "cumtime"   : (((3,-1),              ), "cumulative time"),
            "cumulative": (((3,-1),              ), "cumulative time"),
            "filename"  : (((4, 1),              ), "file name"),
            "line"      : (((5, 1),              ), "line number"),
            "module"    : (((4, 1),              ), "file name"),
            "name"      : (((6, 1),              ), "function name"),
            "nfl"       : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
            "pcalls"    : (((0,-1),              ), "primitive call count"),
            "stdname"   : (((7, 1),              ), "standard name"),
            "time"      : (((2,-1),              ), "internal time"),
            "tottime"   : (((2,-1),              ), "internal time"),
            }

分析结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
         9 function calls in 2.361 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       1    0.000    0.000    2.361    2.361 profile:0(test_code())
       1    0.000    0.000    2.361    2.361 :0(exec)
       1    0.000    0.000    2.361    2.361 <string>:1(<module>)
       1    0.000    0.000    2.361    2.361 <ipython-input-11-a4480b4512b3>:16(test_code)
       2    2.361    1.181    2.361    1.181 <ipython-input-11-a4480b4512b3>:4(cal_factorial)
       1    0.000    0.000    2.328    2.328 <ipython-input-11-a4480b4512b3>:13(functionB)
       1    0.000    0.000    0.033    0.033 <ipython-input-11-a4480b4512b3>:10(functionA)
       1    0.000    0.000    0.000    0.000 :0(setprofile)
       0    0.000             0.000          profile:0(profiler)

各字段含义:

  • ncalls:调用次数
  • tottime:总时间(不包括子函数时间)
  • cumtime:累计时间(包括子函数时间)
  • percall:每次调用时间=总时间/调用次数
  • filename:文件名
  • lineno:行号
  • function:函数名

从分析结果中可以看出,本次运行test_code一共用了2.361秒,functionA用了0.033秒,functionB用了2.328秒,但他们函数本身都未消耗时间,时间都消耗在调用子函数:cal_factorial上。

This post is licensed under CC BY 4.0 by the author.