文章目录
概述
运行时间分析工具(查看是哪些语句占用cpu较高)
测试环境分析(执行程序时导包引入)
- cprofile
- https://docs.python.org/zh-cn/3/library/profile.html
- line_profile
生产环境分析(跟踪已经运行的python程序)
- py-spy
运行内存分析工具
测试环境分析(执行程序时导包引入)
- memory_profile
生产环境分析(跟踪已经运行的python程序
- pyrasite
- filprofiler(仅支持python3)
py-spy(py运行时cpu占用时间分析,火焰图)
概述
如果你的python脚本执行很慢,有很多种方法对它的性能进行分析,比如cProfile就很好。
如果你想对正在运行的python进程进行性能分析,cProfile则无能为力,因为程序已经在运行,如果是生产环境,不可能为了配合你分析性能而重新启动程序。
Py-spy可以对正在运行的python进程进行分析,其原理是获取调用堆栈,在linux系统下,通过process_vm_readv,在mac下使用vm_read,在windows上使用ReadProcessMemory。
它每秒采样多次,所以它有很好的机会看到你的程序随着时间的推移将处于各种状态。为了提高速度,它是用Rust编写的。
参考
安装py-spy
- pip install py-spy
使用
dump
通过获取调用堆栈,可以了解程序内部的执行情况,了解函数之间的调用关系
- py-spy dump --pid 21512
- 当一个py进程中起了多个线程,用ps看不出来哪个线程是执行那些py函数了,这是执行这边命令,可以看到每个线程的调用栈,这样就可以比较清晰的看到每个线程是执行哪些代码。
- py-spy dump -l --pid 33131
- -l可以看出每个栈帧的local变量
top
top 命令可以展示哪些函数被调用执行的次数最多。类似于top命令展示的结果
- py-spy top --pid 22135
- 其中一些字段的含义
- OwnTime(from chatgpt),不包含子函数
- py-spy top 显示的 “OwnTime” 列表示每个线程在采样期间花费在自身函数调用上的时间。换句话说,它表示线程在执行当前函数时所占用的 CPU 时间百分比。
- “OwnTime” 可以帮助你识别哪些线程在特定函数上消耗了较多的 CPU 时间。通过观察 “OwnTime” 较高的线程,你可以找出可能导致性能问题的代码部分。请注意,“OwnTime” 是一个近似值,因为它基于采样分析器在特定时间间隔内收集的数据。
- TotalTime,自身+子函数,一个总的cpu占用时间
- py-spy top 显示的 “TotalTime” 列表示每个线程在采样期间花费在当前函数及其所有子函数调用上的时间。换句话说,它表示线程在执行当前函数以及该函数调用的其他函数时所占用的 CPU 时间百分比。
- “TotalTime” 可以帮助你识别哪些线程在整个调用栈上消耗了较多的 CPU 时间。通过观察 “TotalTime” 较高的线程,你可以找出可能导致性能问题的代码部分。请注意,“TotalTime” 是一个近似值,因为它基于采样分析器在特定时间间隔内收集的数据。
- OwnTime(from chatgpt),不包含子函数
python火焰图
火焰图可以比较清晰的看见各个函数和其子函数的cpu占用时间,方便分析程序性能瓶颈和cpu异常高的情况。生成的profile.svg文件可以在浏览器里查看。
- py-spy record -o profile.svg --pid 33131
- 制作运行中的程序
- py-spy record -o profile.svg python3 test.py
- 执行程序时制作
pyrasite(运行时,嵌入程序,运行时内存泄露分析)
概述
pyrasite 是一个 Python 库,它允许你在运行时注入代码到一个正在运行的 Python 进程中。这对于调试、性能分析和修改运行时行为非常有用,尤其是在无法停止或重新启动进程的情况下。
包含三个命令行 pyrasite / pyrasite-shell / pyrasite-memory-viewer
pyrasite 的主要功能包括:
- 代码注入:你可以将任意 Python 代码注入到正在运行的进程中,以便在不重启进程的情况下修改其行为。
- 内存分析:pyrasite 集成了一个名为 Meliae 的内存分析器,可以帮助你找出内存泄漏和其他内存相关问题。
- 性能分析:pyrasite 可以与 cProfile 集成,以便在运行时对 Python 进程进行性能分析。
要使用 pyrasite,你需要安装它(pip install pyrasite),然后使用 pyrasite 命令行工具将代码注入到目标进程。例如,要将代码注入到进程 ID 为 12345 的进程中,你可以运行:
pyrasite 12345 my_script.py
其中 my_script.py 包含要注入的 Python 代码。
请注意,在使用 pyrasite 时要谨慎,因为注入的代码可能会导致目标进程崩溃或产生不可预期的副作用。在生产环境中使用 pyrasite 之前,请确保在测试环境中充分测试。
安装pyrasite
- pip install pyrasite
pyrasite-shell 嵌入运行py程序的工具(特别适合做python单元自测)
- pyrasite-shell pid 可以嵌入进程,进入py命令行的界面
- 下面是基于pyrasite-shell嵌入进程后的分析工具
gc - 查看无法回收的垃圾
import gc
# 产看gc是否开启
gc.isenabled()
# 手动进行垃圾回收
gc.collect()
# 查看无法被释放的对象
gc.garbage # 这里打印出无法被释放的对象
objgraph - 对象统计, 对象调用链查看
- 实际排查好像也没起到太大作用,不知道是不是自己没用好0.0
# pip3 install objgraph
import objgraph
# 查看数量最多的对象
objgraph.show_most_common_types(limit=20)
# object增长统计
- 用logger输出
result = objgraph.growth(limit=10, shortnames=True)
if result:
width = max(len(name) for name, _, _ in result)
for name, count, delta in result:
logger.info('%-*s%9d %+9d\n' % (width, name, count, delta))
- 直接打印
objgraph.show_growth()
guppy/guppy3(heapy)
heapy通过对python程序中的kinds(type、class、attribute、module)进行等价划分(等价划分就是通过一些定义来判断这些对象是否可以认为是一样的,然后就把一样的放到同一个集合中,这样就能够很好的对python程序中的大量对象进行统计和展现),进行统计归类,然后将统计后的结果展示出来,以供了解python程序中的内存都存储了些什么,各占的比例是多少。同时还提供了多种方法来查看这些对像的引用信息,以了解这些对象是被谁引用了而导致其一直存在内存中。
实际排查好像也没起到太大作用,不知道是不是自己没用好0.0
kinds划分的方法(分类器):
-
p.heap().bytype:不区分基类是object和非object的划分。
-
p.heap().byclodo:(Class or dict owner)(这是默认的划分方式)
-
p.heap().byclass:表示通过class来统计 与byclodo的区别是:clodo更细一些,会通过dict关系来分类。
-
p.heap().bymodule:以模块为划分来统计对象
-
p.heap().byrcs:表示Referrers by Kind。以引用的所属kind来做划分。比如对象A中有b属性为‘b’,c属性为‘c’, 那么‘b’和‘c’是refer by A,就会被统计到一类中了。
-
p.heap().byvia:以引用来做划分,最后一列显示的信息引用名,比如a=‘a’,显示为a
-
p.heap().byid:通过id划分来统计对象,每一个对象都会给以一个id,最后一列显示id,或者是详细信息,如str显示的是str的内容。重点:可以用于查找到id的最短引用路径
-
p.heap().bysize:通过对象的所占的内存大小做统计对象
-
使用参考:
# pip3 install guppy3(python2中使用guppy也是可以的)
from guppy import hpy
hp=hpy()
hp.heap()
# 打印对象的被哪些引用
heap = hp.heap() # 返回heap内存详情
references = heap[0].byvia # byvia返回该对象的被哪些引用, heap[0]是内存消耗最大的对象
print references
pympler - 对象数量/大小统计,获取变化量(Python3才支持)
# pip3 install pympler
from pympler import tracker, muppy, summary
tr = tracker.SummaryTracker()
tr.print_diff() # 第一次打印,展示进程间所有对象,按大小排序,第二次打印,显示增量。
# 对象统计
all_objects = muppy.get_objects()
sum1 = summary.summarize(all_objects)
sum_str = summary.print_(sum1)
tracemalloc python3自带,据说py2支持的不好
memory_profile(离线定位内存泄露利器)
作用:memory_profiler是用来分析每行代码的内存使用情况
安装
pip install memory_profiler
python2支持的最后一个版本
memory_profiler 的最后一个支持 Python 2 的版本是 0.55.0。你可以使用以下命令来安装这个版本:pip2 install memory_profiler==0.55.0
安装依赖问题
如果出现 #include <Python.h>报错,需安装:sudo yum install gcc python-devel
mprof
安装后,会自动安装mprof命令行工具
memory_profiler的使用
参考:
使用方法1
只使用装饰器,不import memory_profiler。给目标函数加上 @profile 装饰器,执行代码时,给 Python 解释器传递参数 -m memory_profiler ,来加载 memory_profiler 模块。
运行方式: python -m memory_profiler memory_profiler_test.py
#coding:utf8
@profile
def test1():
c=0
for item in xrange(100000):
c+=1
print c
if __name__=='__main__':
test1()
输出结果
rgc@rgc:~/baidu_eye/carrier/test$ python -m memory_profiler memory_profiler_test.py
100000
Filename: memory_profiler_test.py
Line # Mem usage Increment Line Contents
================================================
5 21.492 MiB 21.492 MiB @profile
6 def test1():
7 21.492 MiB 0.000 MiB c=0
8 21.492 MiB 0.000 MiB for item in xrange(100000):
9 21.492 MiB 0.000 MiB c+=1
10 21.492 MiB 0.000 MiB print c
名词含义为
- Mem usage: 内存占用情况
- Increment: 执行该行代码后新增的内存
使用方法2
**使用装饰器,import memory_profiler。**给目标函数加上 @profile 装饰器,import memory_profiler,执行时不需要传递参数。
from memory_profiler import profile
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
python example.py
使用方法3(推荐,分析内容存储在文件中,方便分析守护进程)
1.先导入: from memory_profiler import profile
2.函数前加装饰器: @profile(precision=4,stream=open(‘memory_profiler.log’,‘w+’))
参数含义:
- precision:精确到小数点后几位
- stream:此模块分析结果保存到 ‘memory_profiler.log’ 日志文件。如果没有此参数,分析结果会在控制台输出
运行方式:直接跑此脚本 python memory_profiler_test.py。在不需要分析时,注释装饰器部分
#coding:utf8
from memory_profiler import profile
@profile(precision=4,stream=open('memory_profiler.log','w+'))
# @profile
def test1():
c=0
for item in xrange(100000):
c+=1
print c
if __name__=='__main__':
test1()
使用方法4(使用mprof,基于3的可视化)
脚本代码和方法二一样,但是 运行方式不同
mprof run memory_profiler_test.py: 分析结果会保存到一个 .dat格式文件中
mprof plot: 把结果以图片到方式显示出来(直接在本目录下运行此命令即可,程序会自动找出.dat文件) (要安装 pip install matplotlib)
mprof clean: 清空所有 .dat文件
python cpu飚高排查
概述
- 运行时可以使用py-spy来生成火焰图观察。
- TIPS:自己测试,内存占用过高时,clone(fore进程时)、exec系统调用会变慢,且cpu占用率变高,得注意内存过高时这里对cpu高的影响
python内存泄露排查
概述
python内存泄漏的主要原因
- an unhandled traceback object that is keeping an entire stack frame alive, even though the function is no longer running。举例:若将异常作为全局变量重复抛出,traceback对象会一直得不到释放,该对象的traceback链会越来越长。
- 全局对象的错误使用
- 自定义的 del 方法
- 对象的循环引用
- db连接未关闭。The garbage collector can’t see the MySQL resources involved in the cursor. MySQL doesn’t know that the Python side was released unless the close() method is called explicitly.
使用pyrasite下面的那一套工具好像用处不大,看不出来啥(在python2看不出来啥,没试过python3)。没辙的话就只能使用将内存dump下来分析的那一套流程了(但是也不太好用)。
最理想的情况是应该找到复现手法,然后离线使用memory_profiler去定位内存泄露代码具体位置了。