背景

近日利用 Python 脚本处理大量的数据时,由于每天定时自动调度执行,发现一段时间后机器的内存一直不会被自动释放导致程序被 killed,所以希望有一个工具对自己的代码进行内存诊断。

Python 自带内存管理机制,例如 with ··· as ··· 命令等,不需自己管理内存,但有些情况下可能导致内存泄漏或者OOM问题。

📢 当然也可以通过其它命令手动释放内存,例如

  • 通过 del 操作删除临时变量,但是该操作不会立即释放内存,只是将引用计数减一。
  • 通过 gc.collect() 暴力回收内存,但是会导致程序暂停来回收内存从而严重影响性能。

因此需要一个工具来监控自己的代码,可以了解代码中内存如何分配?是否存在无法释放的内存,或者多少内存会很快被释放?进程在高峰时占用多少内存,在低谷时占用多少内存?

内存查看工具

  • 在程序执行时我们可以通过 top 或者 htop 命令来查看进程的内存使用情况

    🧐 此处推荐 htop 命令对可视化更加友好,Mac 通过 brew install htop 进行安装,下面是两个可视化的对比效果。

    top 命令

    htop 命令

    📢 top 命令只能统计当前整个进程的内存使用情况,不方便从更多的维度诊断问题,例如从时间或者模块的维度进行内存诊断。而

  • 通过 psutil 模块统计代码内存使用情况。

    psutil 可以和代码结合,但是对代码逻辑有严重的影响,需要插入很多额外的代码来计算每个函数的内存占用情况。

那么有没有其它工具可以尽可能少写代码又可以自动监控每个函数执行时占用的内存呢?答案是有的。Python 的 memory_profiler 模块只需给目标函数写上装饰符即可分析代码的内存使用情况!

memory_profiler 简介

memory_profiler 是基于 psutil 的内存监控模块。

官方教程参考:https://github.com/pythonprofilers/memory_profiler

This is a python module for monitoring memory consumption of a process as well as line-by-line analysis of memory consumption for python programs. It is a pure python module which depends on the psutil module.

安装方法如下:

1
$ pip install -U memory_profiler

memory_profiler 有两种应用场景,三种使用方式。

两种应用场景:

  • 逐行的内存使用分析
  • 时间维度的内存使用分析

三种使用方式:

  • 前两种是针对逐行的内存使用分析

  • 另外一种针对时间维度的内存使用分析。

只使用装饰器,不 import memory_profiler

给目标函数加上 @profile 装饰器,执行代码时,给 Python 解释器传递参数 -m memory_profiler 来加载 memory_profiler 模块。

1
2
3
4
5
6
7
8
9
10
11
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a

if __name__ == '__main__':
my_func()

python -m memory_profiler example.py

使用装饰器,import memory_profiler。

给目标函数加上 @profile 装饰器,import memory_profiler,执行时不需要传递参数。

1
2
3
4
5
6
7
8
9
10
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

时间维度的内存使用分析

使用 mprof 执行程序在时间维度分析进程的内存使用情况。

下面介绍了一共有四种情况,分别是:单进程,多进程,记录子进程内存占用,多进程并记录子进程内存占用。

1
2
3
4
mprof run <executable>
mprof run --multiprocess <executable>
mprof run --include-children <executable>
mprof run --include-children --multiprocess <executable>

执行完成后,会生成一个 .dat 文件,类似mprofile_20200329173152.dat

要绘制内存在时间维度的使用情况,需要安装 matplotlib,然后执行 mprof plot \(直接执行会读取最新的 .dat 文件、):

1
2
mprof plot
mprof plot mprofile_20200329173152.dat
内存使用

也可以查看火焰图:

1
mprof plot --flame mprofile_20200329173152.dat

如果是多进程程序还可以查看每个子进程的占用的内存:

memory_profiler 还提供了 API 接口和 IPython 集成的使用方法,十分之强大。目前可以满足我的需求,如有其它需求可以参考 官方教程