当前位置: 首页 > 科技观察

如何解决Python中的内存问题?

时间:2023-03-15 14:26:52 科技观察

【.com速译】发现应用程序内存不足是开发人员遇到的最糟糕的问题之一。内存问题通常很难诊断和修复,但在Python中尤其困难。Python的自动垃圾收集功能让这门语言很容易上手,但是当出现问题时,开发人员不知道如何识别和修复它们。本文介绍如何诊断和修复开源AutoML库EvalML中的内存问题。解决内存问题没有诀窍,但希望开发者,尤其是Python开发者,能够学习到以后遇到此类问题时可以使用的工具和最佳实践。什么是内存泄漏?任何编程语言最重要的特性之一就是能够在计算机内存中存储信息。每当您的程序创建一个新变量时,它都会分配一些内存来存储变量的内容。内核为程序定义了访问计算机CPU、内存和磁盘存储等资源的接口。每种编程语言都提供了要求内核分配和释放内存块以供正在运行的程序使用的方法。当程序要求内核留出一块内存供使用时,就会发生内存泄漏,但随后由于错误或崩溃,程序不再告诉内核程序何时使用完该内存。在这种情况下,内核会继续认为被遗忘的内存块仍然被正在运行的程序使用,其他程序无法访问这些内存块。如果在运行程序时重复发生相同的泄漏,被遗忘的内存总量会变得巨大,消耗计算机的大部分内存!在这种情况下,如果程序随后尝试请求更多内存,内核会抛出“内存不足”错误,程序将停止运行,换句话说就是“崩溃”。因此,在你编写的程序中发现并修复内存泄漏很重要,否则程序最终可能会耗尽内存而崩溃,也可能导致其他程序崩溃。第1步:确定是否是内存问题应用程序崩溃的原因有多种:可能是运行代码的服务器崩溃了,也可能是代码本身存在逻辑错误,因此确定手头的问题是内存问题很重要问题。EvalML性能测试悄然崩溃。突然,服务器停止记录进度,作业悄悄停止。服务器日志将显示编程错误的任何堆栈跟踪,所以我有一种预感:崩溃是由作业消耗所有可用内存引起的。我再次运行性能测试,但这次启用了Python的内存分析器,以获取内存使用情况的图表。测试再次崩溃,当我查看内存图时,我发现:图1.性能测试的内存使用内存使用逐渐稳定,但随后达到8GB!我知道应用服务器有8GB内存,所以图表证实我们内存不足。另外,当内存稳定时,我们使用了大约4GB的内存,但是之前版本的EvalML使用了大约2GB的内存。出于某种原因,当前版本使用的内存大约是平时的两倍。现在需要找出原因。第2步:使用最小示例在本地重现内存问题查明内存问题的原因需要大量的试验和迭代,因为答案通常并不明显。如果是这样,您可能不会将其写入您的代码!出于这个原因,我认为用尽可能少的代码行重现问题很重要。这个最小的示例允许您在修改代码时在探查器下快速运行它以查看是否取得进展。我从经验中知道,在我看到大峰值时,该应用程序运行的出租车数据集有150万行。我将应用程序精简为仅在该数据集上运行的部分。我看到像上面那样的峰值,但这次使用了10GB的内存!看到这一点,我知道有一个足够好的最小示例可以深入研究。图2.出租车数据集在本地再现的内存使用情况第3步:找到分配最多内存的代码行一旦问题被隔离到尽可能小的代码块,我们就可以看到程序在哪里分配了最多的内存。这使您更容易重构代码和修复问题。filprofiler是一个优秀的Python工具。它显示了应用程序中每一行代码在内存使用高峰期的内存分配情况。下面是本地示例的输出:图3.fil-profilefilprofiler的输出根据内存分配对应用程序中的代码行(以及依赖项的代码行)进行排序。线越长越红,分配的内存越多。分配最多内存的代码行用于创建pandas数据帧(pandas/core/algorithms.py和pandas/core/internal/managers.py),总计4GB数据!我在这里截断了filprofiler的输出,但它能够将pandas代码追溯到使用EvalML创建Pandas数据帧的代码。是的,EvalML创建了Pandas数据帧,但这些数据帧在整个AutoML算法中都是短暂的,一旦不再使用就应该被释放。由于情况并非如此,并且这些数据帧在内存中的时间足够长,我认为最新版本带来了内存泄漏。第4步:识别泄漏对象在Python中,泄漏对象是在您使用完它们后未被Python的垃圾收集器释放的对象。由于Python使用引用计数作为主要的垃圾回收算法之一,因此这些泄漏的对象通常是由对象持有引用的时间过长造成的。此类对象很难找到,但一些Python工具可以简化搜索。第一个工具是垃圾收集器的gc.DEBUG_SAVEALL标志。如果设置了此标志,垃圾收集器会将无法访问的对象存储在gc.garbage列表中。这使您可以进一步调查这些对象。第二个工具是objgraph库。一旦对象在gc.garbage列表中,我们就可以根据pandas数据帧过滤该列表,并使用objgraph查看哪些其他对象引用这些数据帧,将它们保存在内存中。这是我在可视化其中一个数据帧后看到的对象图的一个子集:图4.使用内存图的Pandas数据帧显示循环引用导致内存泄漏这就是我一直在寻找的确凿证据!数据框由一个循环引用的PandasTableAccessor创建并引用自身,因此这会将对象保存在内存中,直到Python的垃圾收集器运行并释放它。(可以通过dict、PandasTableAccessor、dict和_dataframe跟踪循环。)这对EvalML来说是有问题的,因为垃圾收集器将这些数据帧保存在内存中的时间太长,以至于我们耗尽了内存!我能够将PandasTableAccessor追溯到Woodwork库,并且这个问题已提交给维护者。在新版本中修复问题后,他们向pandas存储库提交了相关问题单,这是开源生态系统中协作的一个例子。Woodwork更新发布后,我可视化了同一数据框的对象图,循环消失了!图5.woodwork升级后pandas数据框的对象图。没有更多的循环!第5步:验证修复是否有效我在EvalML中升级Woodwork版本后测量了应用程序的内存使用情况。事实证明,内存使用量现在还不到以前的一半!图6.修复后性能测试的内存使用情况