如何对Python脚本进行性能分析

如何对Python脚本进行性能分析

技术背景

在开发Python程序时,为了优化代码性能,我们需要找出程序中哪些部分运行缓慢。Python提供了多种性能分析工具,帮助开发者定位性能瓶颈。

实现步骤

使用cProfile进行性能分析

  1. 在代码中调用
1
2
import cProfile
cProfile.run('foo()')
  1. 从命令行运行脚本时调用
1
python -m cProfile myscript.py
  1. 运行模块时调用
1
python -m cProfile -m mymodule

使用pycallgraph生成可视化调用图

  1. 安装依赖
1
pip install pycallgraph

同时需要安装GraphViz
2. 从命令行运行

1
pycallgraph graphviz -- ./mypythonscript.py
  1. 在代码中指定部分代码进行分析
1
2
3
4
5
from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

with PyCallGraph(output=GraphvizOutput()):
code_to_profile()

使用SnakeViz进行可视化分析

  1. 安装
1
pip install snakeviz
  1. 生成性能数据文件
1
python -m cProfile -o temp.dat <PROGRAM>.py
  1. 使用SnakeViz打开数据文件
1
snakeviz temp.dat

对多线程代码进行性能分析

可以使用threading.setprofile()函数,也可以创建自定义的threading.Thread子类:

1
2
3
4
5
6
7
8
9
10
11
import threading
import cProfile

class ProfiledThread(threading.Thread):
# Overrides threading.Thread.run()
def run(self):
profiler = cProfile.Profile()
try:
return profiler.runcall(threading.Thread.run, self)
finally:
profiler.dump_stats('myprofile-%d.profile' % (self.ident,))

核心代码

自定义性能分析装饰器

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
38
39
40
41
42
43
44
45
import cProfile, pstats

class _ProfileFunc:
def __init__(self, func, sort_stats_by):
self.func = func
self.profile_runs = []
self.sort_stats_by = sort_stats_by

def __call__(self, *args, **kwargs):
pr = cProfile.Profile()
pr.enable() # this is the profiling section
retval = self.func(*args, **kwargs)
pr.disable()

self.profile_runs.append(pr)
ps = pstats.Stats(*self.profile_runs).sort_stats(self.sort_stats_by)
return retval, ps

def cumulative_profiler(amount_of_times, sort_stats_by='time'):
def real_decorator(function):
def wrapper(*args, **kwargs):
nonlocal function, amount_of_times, sort_stats_by # for python 2.x remove this row

profiled_func = _ProfileFunc(function, sort_stats_by)
for i in range(amount_of_times):
retval, ps = profiled_func(*args, **kwargs)
ps.print_stats()
return retval # returns the results of the function
return wrapper

if callable(amount_of_times): # incase you don't want to specify the amount of times
func = amount_of_times # amount_of_times is the function in here
amount_of_times = 5 # the default amount
return real_decorator(func)
return real_decorator

import time

@cumulative_profiler
def baz():
time.sleep(1)
time.sleep(2)
return 1

baz()

最佳实践

  • 选择合适的工具:根据分析需求选择合适的工具,如简单的函数时间统计可以使用cProfile,需要可视化调用图可以使用pycallgraph
  • 多次运行取平均值:由于系统环境等因素的影响,一次运行的结果可能不准确,可以多次运行取平均值。

常见问题

  • 多线程分析不准确:默认情况下,性能分析工具只对主线程有效,要对多线程进行分析,需要使用特定的方法,如threading.setprofile()或自定义线程类。
  • 可视化工具安装问题:一些可视化工具需要依赖其他软件,如pycallgraph需要安装GraphViz,安装时可能会遇到问题,需要根据具体错误信息进行解决。

如何对Python脚本进行性能分析
https://119291.xyz/posts/how-to-profile-a-python-script/
作者
ww
发布于
2025年5月27日
许可协议