[Py]Python性能分析工具

时间分析

time 命令

用*nix自带的系统命令

1
2
3
4
5
$ time python3 reg.py 

real 0m1.617s
user 0m1.504s
sys 0m0.112s

sys 系统调用时间
user 用户空间花费时间
real 实际时间
如果user + sys < real 说明时间被花费在IO上。

profile和cProfile

python自带了两个函数时间分析工具,cProfile和profile。cProfile是纯C写的,所以用起来快了很多。
查看帮助:

1
2
3
4
5
6
7
8
9
$ python3 -m cProfile -h
Usage: cProfile.py [-o output_file_path] [-s sort] scriptfile [arg] ...

Options:
-h, --help show this help message and exit
-o OUTFILE, --outfile=OUTFILE
Save stats to <outfile>
-s SORT, --sort=SORT Sort order when printing to stdout, based on
pstats.Stats class

-o 输出的文件只能给pstats.Stats类使用。
-s 是对结果按照关键字排序,关键字有
calls, cumulative, file, line, module, name, nfl, pcalls, stdname, time

尝试分析一个测正则匹配次数的小程序:

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
$ python3 -m cProfile -s time exp.py
4899025 function calls in 3.879 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
1 1.583 1.583 3.879 3.879 exp.py:1(<module>)
1 0.777 0.777 0.859 0.859 {method 'readlines' of '_io._IOBase' objects}
2415450 0.720 0.000 0.720 0.000 {method 'search' of '_sre.SRE_Pattern' objects}
2415450 0.717 0.000 0.717 0.000 {method 'strip' of 'str' objects}
33967 0.048 0.000 0.048 0.000 {built-in method _codecs.utf_8_decode}
33967 0.033 0.000 0.082 0.000 codecs.py:318(decode)
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {built-in method io.open}
1 0.000 0.000 0.000 0.000 sre_parse.py:491(_parse)
1 0.000 0.000 0.000 0.000 sre_compile.py:412(_compile_info)
1 0.000 0.000 0.000 0.000 sre_compile.py:531(compile)
1 0.000 0.000 0.000 0.000 re.py:278(_compile)
1 0.000 0.000 0.000 0.000 sre_parse.py:167(getwidth)
1 0.000 0.000 0.000 0.000 sre_compile.py:64(_compile)
15 0.000 0.000 0.000 0.000 sre_parse.py:226(__next)
65 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 sre_compile.py:391(_generate_overlap_table)
14 0.000 0.000 0.000 0.000 sre_parse.py:247(get)
1 0.000 0.000 0.000 0.000 sre_parse.py:819(parse)
14 0.000 0.000 0.000 0.000 sre_parse.py:165(append)
1 0.000 0.000 0.000 0.000 sre_parse.py:217(__init__)
1 0.000 0.000 0.000 0.000 sre_parse.py:429(_parse_sub)
26 0.000 0.000 0.000 0.000 {built-in method builtins.len}
...

对结果做了运行时间的排序。输出的表格中
ncalls为函数运行次数
tottime为函数自身运行时间(不包括内部其他函数)
percalltottime/ncalls
cumtime为函数总运行时间(包括其他函数、递归)
percallcumtime/ncalls

line_profiler: 按行查看

安装:
pip3 install line_profiler

安装后便可以使用kernprof分析性能, 先在要分析的函数上加修饰器@profile,然后运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ python3 -m kernprof -l -v exp.py 
10
Wrote profile results to exp.py.lprof
Timer unit: 1e-06 s

Total time: 6.26415 s
File: exp.py
Function: main at line 1

Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 @profile
2 def main():
3 1 6 6.0 0.0 import re
4 1 673 673.0 0.0 reg = re.compile(r'DO_PATH_REPLAN')
5 1 1 1.0 0.0 count = 0
6 1 45 45.0 0.0 with open('log.46.log') as f:
7 2415451 2370592 1.0 37.8 for line in f.readlines():
8 2415450 1674712 0.7 26.7 line = line.strip()
9 2415450 2218037 0.9 35.4 if reg.search(line) is not None:
10 10 6 0.6 0.0 count += 1
11 1 79 79.0 0.0 print(count)

内存分析

memory_profiler

安装:

1
2
pip3 install memory_profiler
pip3 install psutil

同样,先在要分析的函数上加修饰器@profile,然后运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ python3 -m memory_profiler exp.py
10
Filename: exp.py

Line # Mem usage Increment Line Contents
================================================
1 14.891 MiB 0.000 MiB @profile
2 def main():
3 14.891 MiB 0.000 MiB import re
4 14.891 MiB 0.000 MiB reg = re.compile(r'DO_PATH_REPLAN')
5 14.891 MiB 0.000 MiB count = 0
6 14.891 MiB 0.000 MiB with open('log.46.log') as f:
7 428.734 MiB 413.844 MiB for line in f.readlines():
8 428.734 MiB 0.000 MiB line = line.strip()
9 428.734 MiB 0.000 MiB if reg.search(line) is not None:
10 428.734 MiB 0.000 MiB count += 1
11 15.250 MiB -413.484 MiB print(count)