python老司机必备-内存泄露分析优化

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: python老司机必备-内存泄露分析优化

13.png发现应用程序正在耗尽内存是开发人员需要面对的棘手问题之一。内存问题通常很难诊断和修复,并且在Python中更难。Python的自动垃圾收集使它很容易上手并易学,但是它很善于避开障碍,以至于当它不能如预期的那样工作时,开发人员可能会对如何识别和修复问题感到困惑。


在文中,我将展示如何在EvalML中诊断和修复内存问题,EvalML是Alteryx创新实验室开发的开源AutoML库。没有解决内存问题的魔法配方,但我希望开发人员,特别是Python开发人员,能够了解在将来遇到这类问题时可以利用的工具。


读完这篇博文后,你应该遵循以下几点:


  • 为什么查找和修复程序中的内存问题很重要,
  • 什么是循环引用,为什么它们会导致Python中的内存泄漏,以及
  • 了解Python的内存分析工具,以及可以用来识别内存问题原因的一些步骤。

设置舞台

EvalML团队在发布我们的包的新版本之前运行一套性能测试,以捕获任何性能回归。这些性能测试包括在各种数据集上运行我们的AutoML算法,测量我们的算法达到的分数以及运行时间,并将这些指标与我们之前发布的版本进行比较。


有一天我正在运行测试,突然应用程序崩溃了。发生了什么事?


Step 0 - 什么是内存,什么是泄漏?

任何编程语言最重要的功能之一就是它在计算机内存中存储信息的能力。每当程序创建一个新变量时,它就会分配一些内存来存储该变量的内容。


内核为程序定义了一个接口来访问计算机的cpu、内存、磁盘存储等等。每一种编程语言都提供了要求内核分配和释放内存块以供运行程序使用的方法。


当程序要求内核留出一块内存来使用时,就会发生内存泄漏,但是由于错误或崩溃,程序永远不会告诉内核它何时结束使用该内存。在这种情况下,内核将继续认为被遗忘的内存块仍在被正在运行的程序使用,而其他程序将无法访问这些内存块。


如果在运行一个程序时重复发生相同的泄漏,那么被遗忘的内存的总大小可能会变得非常大,从而消耗计算机内存的很大一部分!在这种情况下,如果程序试图请求更多内存,内核将引发一个“内存不足”错误,程序将停止运行,或者换句话说,“崩溃”。


因此,在您编写的程序中找到并修复内存泄漏非常重要,因为如果不这样做,您的程序可能最终耗尽内存并崩溃,或者可能导致其他程序崩溃。


Step 1: 确定这是一个内存问题

应用程序崩溃的原因有很多——可能是运行代码的服务器崩溃了,也可能是代码本身存在逻辑错误——所以确定当前的问题是内存问题是很重要的。


EvalML性能测试以一种诡异的安静方式崩溃。突然,服务器停止了记录进度,工作也悄然完成了。服务器日志将显示由编码错误引起的任何堆栈跟踪,因此我预感这个无声的崩溃是由使用了所有可用内存的作业引起的。


我再次运行了性能测试,但这一次启用了Python的内存分析器,以获得一段时间内内存使用情况的图表。测试再次崩溃,当我查看内存图时,我看到了这个:


14.png


我们的内存使用在一段时间内保持稳定,但随后它达到了8g !我知道我们的应用服务器有8g的RAM,所以这个配置文件证实了我们的内存即将耗尽。此外,当内存稳定时,我们将使用大约4 GB的内存,但是我们之前的

EvalML版本使用了大约2 GB的内存。因此,由于某些原因,当前版本使用的内存是正常版本的两倍。


现在我需要找出原因。


Step 2: 用一个最小的例子在本地产生内存问题

找出内存问题的原因需要大量的实验和迭代,因为答案通常并不明显。如果是的话,您可能就不会把它写进代码中了!出于这个原因,我认为用尽可能少的代码行重现这个问题是很重要的。这个最小的示例使您可以在修改代码的同时在分析器下快速运行它,以查看是否有进展。


在我的例子中,根据经验,我知道我们的应用程序运行一个150万行的出租车数据集时,我看到了一个大的峰值。我将我们的应用程序精简到只有运行这个数据集的部分。我看到了一个类似于我上面描述的峰值,但是这次,内存使用达到了10g !


在看到这个之后,我知道有一个足够好的最小的例子来深入研究。


15.png


出租车数据集上本地复制器的内存占用


Step 3:查找分配最多内存的代码行

一旦我们将问题隔离到尽可能小的代码块中,我们就可以看到程序在哪里分配了最多的内存。这可能是您重构代码和修复问题所需的确凿证据。


我认为filprofiler是一个很好的Python工具。它会在内存使用高峰时显示应用程序中每一行代码的内存分配。这是我本地示例的输出:


16.png


文件分析器根据内存分配对应用程序中的代码行(以及依赖项代码)进行排序。线路越长越红,分配的内存就越多。


分配最多内存的行是创建pandas数据帧(pandas/core/algorithms.py和pandas/core/internal/managers.py),总计4 gb的数据!我在这里截断了filprofiler的输出,但是它能够跟踪pandas代码到创建pandas数据帧的EvalML中的代码。


看到这一点有点令人困惑。是的,EvalML创建pandas数据帧,但是这些数据帧在AutoML算法中是短命的,当它们不再使用时就应该被释放。由于情况并非如此,而且这些数据帧在内存中仍然存在足够长的时间,我认为最新版本引入了内存泄漏。


Step 4:识别泄漏对象

在Python上下文中,泄漏对象是指Python的垃圾回收器在执行完回收后没有释放的对象。由于Python使用引用计数作为其主要的垃圾收集算法之一,这些泄漏对象通常是由于对象持有对它们的引用时间过长而导致的。


这些类型的对象很难找到,但是可以利用一些Python工具使方便搜索。第一个工具是gc。垃圾收集器的DEBUG_SAVEALL标志。通过设置这个标志,垃圾收集器将在gc中存储不可到达的对象:垃圾的列表。这将让您进一步研究这些对象。


第二个工具是objgraph库。一旦对象在gc垃圾列表,我们可以过滤这个列表到pandas数据帧,并使用objgraph来查看其他对象引用这些数据帧,并将它们保存在内存中。通过阅读O 'Reilly的这篇博客文章,我得到了这个方法的想法。


这是我在可视化其中一个数据帧时看到的对象图的子集:

17.png

pandas数据帧使用的内存图,显示导致内存泄漏的循环引用。


这就是我要找的确凿证据!数据帧通过一个叫做PandasTableAccessor的东西对自身进行引用,它创建了一个循环引用,因此这会将对象保存在内存中,直到Python的垃圾收集器运行并能够释放它。(你可以通过dict, PandasTableAccessor, dict, _dataframe跟踪循环。)这对于EvalML来说是有问题的,因为垃圾收集器将这些数据帧保存在内存中太长时间,导致了内存耗尽!


我能够跟踪PandasTableAccessor到Woodwork库,并将这个问题提交给维护者。他们在一个新版本中修复了这个问题,并将相关问题提交给了pandas库——这是开源生态系统中协作的一个很好的例子。


在Woodwork更新发布后,我可视化了相同数据帧的对象图,循环引用消失了!


18.png

Step 5: 验证修复效果

升级了EvalML中的Woodwork版本后,我测量了应用程序的内存占用。我很高兴地报告,内存使用现在不到以前的一半!


19.png


修复后性能测试的内存


结束语

正如我在这篇文章的开头说的,没有解决内存问题的魔法配方,但是这个案例研究提供了一个通用的框架和一组工具,如果你在将来遇到这种情况,你可以利用。我发现memory-profiler和filprofiler是调试Python内存泄漏利器。


我还想强调的是,Python中的循环引用会增加应用程序的内存占用。垃圾收集器最终将释放内存,但是,正如我们在本例中看到的,循环引用无法被回收随着时间推移,内存就会耗尽!


Python中很容易无意地引入循环引用,我能够在EvalML、scikit- optimization和scipy中找到这种情况。我鼓励你睁大研究仔细分析,看这些循环引用是否必须!


相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
目录
打赏
0
0
0
0
32
分享
相关文章
从混沌到秩序:Python的依赖管理工具分析
Python 的依赖管理工具一直没有标准化,主要原因包括历史发展的随意性、社区的分散性、多样化的使用场景、向后兼容性的挑战、缺乏统一治理以及生态系统的快速变化。依赖管理工具用于处理项目中的依赖关系,确保不同环境下的依赖项一致性,避免软件故障和兼容性问题。常用的 Python 依赖管理工具如 pip、venv、pip-tools、Pipenv、Poetry 等各有优缺点,选择时需根据项目需求权衡。新工具如 uv 和 Pixi 在性能和功能上有所改进,值得考虑。
108 35
金融波动率的多模型建模研究:GARCH族与HAR模型的Python实现与对比分析
本文探讨了金融资产波动率建模中的三种主流方法:GARCH、GJR-GARCH和HAR模型,基于SPY的实际交易数据进行实证分析。GARCH模型捕捉波动率聚类特征,GJR-GARCH引入杠杆效应,HAR整合多时间尺度波动率信息。通过Python实现模型估计与性能比较,展示了各模型在风险管理、衍生品定价等领域的应用优势。
443 66
金融波动率的多模型建模研究:GARCH族与HAR模型的Python实现与对比分析
【强化学习】基于深度强化学习的微能源网能量管理与优化策略研究【Python】
本项目基于深度Q网络(DQN)算法,通过学习预测负荷、可再生能源输出及分时电价等信息,实现微能源网的能量管理与优化。程序以能量总线模型为基础,结合强化学习理论,采用Python编写,注释清晰,复现效果佳。内容涵盖微能源网系统组成、Q学习算法原理及其实现,并提供训练奖励曲线、发电单元功率、电网交互功率和蓄电池调度等运行结果图表,便于对照文献学习与应用。
全面提升Python性能的十三种优化技巧
通过应用上述十三种优化技巧,开发者可以显著提高Python代码的执行效率和性能。每个技巧都针对特定的性能瓶颈进行优化,从内存管理到并行计算,再到使用高效的数值计算库。这些优化不仅能提升代码的运行速度,还能提高代码的可读性和可维护性。希望这些技巧能帮助开发者在实际项目中实现更高效的Python编程。
118 22
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
213 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
29天前
|
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
云数据库实战:基于阿里云RDS的Python应用开发与优化
在互联网时代,数据驱动的应用已成为企业竞争力的核心。阿里云RDS为开发者提供稳定高效的数据库托管服务,支持多种数据库引擎,具备自动化管理、高可用性和弹性扩展等优势。本文通过Python应用案例,从零开始搭建基于阿里云RDS的数据库应用,详细演示连接、CRUD操作及性能优化与安全管理实践,帮助读者快速上手并提升应用性能。
|
3月前
|
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
95 5
Python高性能编程:五种核心优化技术的原理与Python代码
Python图像处理中的内存泄漏问题:原因、检测与解决方案
在Python图像处理中,内存泄漏是常见问题,尤其在处理大图像时。本文探讨了内存泄漏的原因(如大图像数据、循环引用、外部库使用等),并介绍了检测工具(如memory_profiler、objgraph、tracemalloc)和解决方法(如显式释放资源、避免循环引用、选择良好内存管理的库)。通过具体代码示例,帮助开发者有效应对内存泄漏挑战。
58 1

热门文章

最新文章