目录
使用测试套件捕获内存泄漏
资源泄漏是一种令人不快的错误类型。你的程序逐渐使用更多的内存、文件描述符或其他有限的资源。一切似乎都正常——直到你运行程序,结果程序崩溃了。
在许多情况下,你可以通过调整测试套件提前捕获这类错误。或者,在你发现此类错误后,可以使用测试套件来识别导致泄漏的原因。本文将介绍:
- 内存泄漏的示例。
- 何时使用测试套件可能是识别泄漏原因的好方法。
- 如何使用
pytest
捕获泄漏。 - 其他类型的泄漏。
内存泄漏
通常,你的程序会分配一些内存,然后最终释放它。在 Python 中,一旦对象不再有任何引用(或更准确地说,没有来自模块或运行代码的引用),内存会自动释放。在编译语言中,可能需要手动释放内存。
如果你不释放内存会发生什么?随着进程使用越来越多的内存,它最终会变慢、崩溃或被静默杀死。例如,我运行了这个 Python 程序:
$ python -m mypackage
最终它崩溃了;起初我不确定它是否给出了错误消息,因为同时我的终端卡住并不得不被杀死。当我运行 sudo dmesg
时,Linux 内核日志显示了以下内容:
...
[185329.418692] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=user.slice,mems_allowed=0,global_oom,task_memcg=/user.slice/user-1000.slice/user@1000.service/app.slice/app-gnome-emacs-22313.scope,task=python,pid=729871,uid=1000
[185329.418713] Out of memory: Killed process 729871 (python) total-vm:42680276kB, anon-rss:42213556kB, file-rss:1600kB, shmem-rss:0kB, UID:1000 pgtables:83572kB oom_score_adj:100
程序使用了太多内存,因此操作系统杀死了它。
使用测试套件识别内存泄漏
识别内存泄漏的一种方法是使用内存分析器,如 Memray、Fil(更易于使用,但功能较少)或 Sciagraph(专为批处理作业的内存和性能分析设计,适用于开发和生产环境)。但这可能取决于运行时环境和程序类型;例如,Sciagraph 无法帮助你分析生产环境中运行的 Web 服务器,它专为批处理作业设计。
另一种方法是查看是否可以使用测试套件发现内存泄漏。特别是,如果满足以下条件,这种方法可能会奏效:
- 你的测试套件有良好的覆盖率。
- 内存泄漏易于重现,不会只在罕见的情况下发生。当然,你事先不会知道这一点。
- 内存泄漏足够大,可以在运行测试时被注意到。同样,你事先也不会知道这一点。
如果这些条件成立,并且在许多情况下它们确实成立,你可以使用测试套件帮助识别内存(或其他资源泄漏)的来源。特别是对于内存泄漏,对于每个测试:
- 在测试运行之前,测量当前使用的内存量。
- 测试完成后,再次测量内存使用情况。
- 如果第二个数字更高,则表明测试中泄漏了一些内存。
使用 pytest
实现资源泄漏检测
有多种方法可以测量内存使用情况:
tracemalloc
是 Python 内置的,测量粒度非常细;缺点是并非所有第三方编译扩展都集成了tracemalloc
,例如 Polars 的内存分配不会显示在那里。- 另一个选项是
psutil.Process().memory_full_info().uss
,它无法注意到小的分配,但适用于任何库,因为它测量的是进程级别的操作系统信息。
我们将使用 tracemalloc
。
我们的示例包在 mypackage/tests/
中有测试,使用流行的 pytest
测试框架运行:
$ pytest
========