python脚本性能分析
1.python脚本性能分析
cProfile思路
- 使用cProfile模块生成脚本执行的统计信息文件
- 使用pstats格式化统计信息,并根据需要做排序分析处理
- 使用snakeviz图形化页面显示
2.cProfile
使用cProfile模块生成脚本执行的统计信息文件
1.使用cProfile模块生成脚本执行的统计信息文件
python -m cProfile -o /xxx/out.prof test.py
参数说明:
使用模块当做脚本运行:-m cProfile
-o 将profile 结果写入文件而不是标准输出
-s 指定 sort_stats() 排序值之一以对输出进行排序。这仅适用于未提供 -o 的情况
测试的python脚本:test.py
输出信息
ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个percall)等于 tottime/ncalls;
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每个函数调用的具体信息;
### 输出案列
{'message': 'Number of rows affected: 1', 'state': 'SUCCESS', 'revision': 'eb7bc19a99eeda508410f4533967fb0eb25328e2'}
5145 function calls (5069 primitive calls) in 1.370 seconds
Ordered by: internal time
List reduced from 563 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.327 1.327 1.327 1.327 {method 'recv_into' of '_socket.socket' objects}
1 0.024 0.024 0.024 0.024 {built-in method _socket.getaddrinfo}
1 0.007 0.007 0.007 0.007 {method 'connect' of '_socket.socket' objects}
1 0.006 0.006 0.006 0.006 {built-in method _scproxy._get_proxy_settings}
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
2 0.000 0.000 0.000 0.000 {method 'sendall' of '_socket.socket' objects}
2 0.000 0.000 0.000 0.000 {method 'read' of '_io.BufferedReader' objects}
1 0.000 0.000 0.000 0.000 {built-in method _scproxy._get_proxies}
11 0.000 0.000 0.000 0.000 queue.py:121(put)
4 0.000 0.000 0.000 0.000 {method 'subn' of 're.Pattern' objects}
3.使用pstats分析
使用cProfile分析的结果可以输出到指定的文件中,但是文件内容是以二进制的方式保存的,用文本编辑器打开时乱码。所以,Python提供了一个pstats模块,用来分析cProfile输出的文件内容。
pstats.Stats说明
sort_stats排序参数
有效字符串参数 | 有效枚举参数 | 含意 |
---|---|---|
'calls' |
SortKey.CALLS | 调用次数 |
'cumulative' |
SortKey.CUMULATIVE | 累积时间 |
'cumtime' |
N/A | 累积时间 |
'file' |
N/A | 文件名 |
'filename' |
SortKey.FILENAME | 文件名 |
'module' |
N/A | 文件名 |
'ncalls' |
N/A | 调用次数 |
'pcalls' |
SortKey.PCALLS | 原始调用计数 |
'line' |
SortKey.LINE | 行号 |
'name' |
SortKey.NAME | 函数名称 |
'nfl' |
SortKey.NFL | 名称/文件/行 |
'stdname' |
SortKey.STDNAME | 标准名称 |
'time' |
SortKey.TIME | 内部时间 |
'tottime' |
N/A | 内部时间 |
strip_dirs()
这个用于 Stats 类的方法会从文件名中去除所有前导路径信息。 它对于减少打印输出的大小以适应(接近) 80 列限制。 这个方法会修改对象,被去除的信息将会丢失。 在执行去除操作后,可以认为对象拥有的条目将使用“随机”顺序,就像它刚在对象初始化并加载之后一样。 如果 strip_dirs() 导致两个函数名变得无法区分(它们位于相同文件名的相同行,并且具有相同的函数名),那么这两个条目的统计数据将被累积到单个条目中。
使用p.strip_dirs()
输出
/test/venv/bin/python3 /test/t1.py
{'message': 'Number of rows affected: 1', 'state': 'SUCCESS', 'revision': 'eb7bc19a99eeda508410f4533967fb0eb25328e2'}
5145 function calls (5069 primitive calls) in 1.683 seconds
Ordered by: internal time
List reduced from 563 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.404 1.404 1.404 1.404 {method 'recv_into' of '_socket.socket' objects}
1 0.262 0.262 0.262 0.262 {built-in method _socket.getaddrinfo}
1 0.006 0.006 0.006 0.006 {method 'connect' of '_socket.socket' objects}
1 0.005 0.005 0.005 0.005 {built-in method _scproxy._get_proxy_settings}
2 0.000 0.000 0.000 0.000 {method 'sendall' of '_socket.socket' objects}
2 0.000 0.000 0.000 0.000 {method 'read' of '_io.BufferedReader' objects}
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 _scproxy._get_proxies}
11 0.000 0.000 0.000 0.000 queue.py:121(put)
4 0.000 0.000 0.000 0.000 {method 'subn' of 're.Pattern' objects}
未使用p.strip_dirs()
输出
区别
/test/venv/lib/python3.8/site-packages/urllib3/poolmanager.py:263(connection_from_pool_key)
这类信息
/test/venv/bin/python3 /test/t1.py
{'message': 'Number of rows affected: 1', 'state': 'SUCCESS', 'revision': 'eb7bc19a99eeda508410f4533967fb0eb25328e2'}
5145 function calls (5069 primitive calls) in 1.303 seconds
Ordered by: internal time
List reduced from 563 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.263 1.263 1.263 1.263 {method 'recv_into' of '_socket.socket' objects}
1 0.019 0.019 0.019 0.019 {built-in method _scproxy._get_proxy_settings}
1 0.007 0.007 0.007 0.007 {method 'connect' of '_socket.socket' objects}
1 0.003 0.003 0.003 0.003 /test/venv/lib/python3.8/site-packages/urllib3/poolmanager.py:263(connection_from_pool_key)
1 0.002 0.002 0.002 0.002 {built-in method _socket.getaddrinfo}
1 0.001 0.001 0.001 0.001 {built-in method builtins.print}
2 0.001 0.000 0.001 0.000 {method 'read' of '_io.BufferedReader' objects}
1 0.000 0.000 0.000 0.000 {built-in method _scproxy._get_proxies}
147 0.000 0.000 0.000 0.000 /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/os.py:748(encode)
2 0.000 0.000 0.000 0.000 {method 'sendall' of '_socket.socket' objects}
sort_stats()
参考pstats.Stats说明,根据提供的条件对其进行排序。
import pstats
p = pstats.Stats('/xxx/out.prof')
# 根据NAME和FILE进行排序
p.sort_stats(SortKey.NAME, SortKey.FILE)
# 输出全部
p.print_stats()
print_stats()
提供的参数(如果有)可用于将列表限制为重要条目。最初,列表被视为一组完整的概要函数。每个限制要么是一个整数(选择行数),要么是一个介于0.0和1.0之间(包括0.0和1.0)的小数(选择行的百分比),要么是一个将被解释为正则表达式的字符串(模式与打印的标准名称匹配
import pstats
p = pstats.Stats('/xxx/out.prof')
# 输出前10行
p.print_stats(10)
# 首先将打印限制在列表的前10%,然后仅打印属于文件名的函数 .*foo:
p.print_stats(.1, 'foo:')
# 将列表限制为具有文件名的所有函数 .*foo: ,然后继续只打印前10%的内容。
p.print_stats('foo:', .1)
dump_stats()
import pstats
# 保存信息到文件中
p=pstats.Stats('/xxx/out.prof')
# 排序
p.sort_stats('calls')
# 把排序完后的内容输出到新的文件中
p.dump_stats('/xxx/xxx.prof')
print_callers()
import pstats
p=pstats.Stats('/xxx/out.prof')
# 如果想知道有哪些函数调用了sum_num
p.print_callers(0.5, "sum_num")
print_callees()
import pstats
p=pstats.Stats('/xxx/out.prof')
# 查看test()函数中调用了哪些函数
p.print_callees("test")
4.图形化工具
安装snakeviz
pip3 install snakeviz
查看是否安装完成
snakeviz -v
snakeviz 2.1.1
使用snakeviz
使用cProfile输出的文件或者pstats输出的文件进行分析
snakeviz /xxx/xxx.prof
snakeviz web server started on 127.0.0.1:8080; enter Ctrl-C to exit
http://127.0.0.1:8080/snakeviz/%2FUsers%2Flxd670%2Ftest_pg3%2Fa.prof
5.使用代码
函数
def main():
...
if __name__ == '__main__':
import cProfile
import pstats
with cProfile.Profile() as pf:
main()
stats = pstats.Stats(pf)
stats.strip_dirs()
# 按时间排序
stats.sort_stats(pstats.SortKey.TIME)
# 在终端打印前10行信息
stats.print_stats(10)
# 把排序过滤后的信息输出到xxx.prof文件中
stats.dump_stats('./xxx.prof')
协程函数
async def main():
...
if __name__ == '__main__':
import cProfile
import pstats
with cProfile.Profile() as pf:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
stats = pstats.Stats(pf)
stats.strip_dirs()
stats.sort_stats(pstats.SortKey.TIME)
stats.print_stats(10)
stats.dump_stats('./xxx.prof')