如何使用pandas查找内存泄漏

9

我有一个程序,会像下面这样反复循环遍历一个pandas数据帧:

monts = [some months]

for month in months:
  df = original_df[original_df.month == month].copy()
  result = some_function(df)
  print(result)

然而,每次迭代所需的内存仍在增加。
                                           types |   # objects |   total size
================================================ | =========== | ============
             <class 'pandas.core.frame.DataFrame |          22 |      6.54 GB
               <class 'pandas.core.series.Series |        1198 |      4.72 GB
                           <class 'numpy.ndarray |        1707 |    648.19 MB
     <class 'pandas.core.categorical.Categorical |         238 |    368.90 MB
          <class 'pandas.core.indexes.base.Index |         256 |    312.03 MB

================================================ | =========== | ============
             <class 'pandas.core.frame.DataFrame |          30 |      9.04 GB
               <class 'pandas.core.series.Series |        2262 |      7.29 GB
                           <class 'numpy.ndarray |        2958 |    834.49 MB
     <class 'pandas.core.categorical.Categorical |         356 |    569.39 MB
          <class 'pandas.core.indexes.base.Index |         380 |    481.21 MB

你有没有一些关于如何找到内存泄漏的建议?

编辑

注意,每次迭代手动调用 gc.collect()并不能帮助解决问题。

编辑2

这里提供了一个最小化的示例:

import pandas as pd
from numpy.random import randn
 df = pd.DataFrame(randn(10000,3),columns=list('ABC'))
for i in range(10):
    print(i)
    something = df.copy()
    print('#########################')
    print('trying to limit memory pressure')
    from pympler import muppy, summary
    all_objects = muppy.get_objects()
    sum1 = summary.summarize(all_objects)
    summary.print_(sum1)
    print('#########################')

如您所见,这表明内存消耗正在增加。一开始使用9MB,在10次迭代后已经使用了30MB。

编辑3

实际上,@Steven的评论可能有一定道理。

for i in range(10):
    something = df.copy()
    foo_thing = summary.summarize(muppy.get_objects())
    summary.print_(foo_thing)

正在显示问题,然而

for i in range(10):
    something = df.copy()
    summary.print_(summary.summarize(muppy.get_objects()))

一切正常。我该如何找到所有导致问题的变量?我认为这尤其重要,因为在我的实际代码中,其中一些是相当大的pandas.Dataframes。

编辑4

手动添加foo_thing = None后,另一个脚本也能正常工作。问题在于 - 如何高效地找到所有这样的情况。Python不应该自动识别不再使用的变量吗?

编辑5

当引入像下面这样的函数时:

def do_some_stuff():
    foo_thing = summary.summarize(muppy.get_objects())
    summary.print_(foo_thing)

for i in range(10):
    something = df.copy()
    do_some_stuff()

内存泄漏问题似乎已经得到了解决。

编辑6

实际上,内存泄漏问题并没有得到解决。好的一面是,summary现在不再报告内存消耗急剧增长的情况。不好的一面是:任务管理器/活动监视器告诉我事实并非如此 - 而且Python程序最终会崩溃。


我不确定这是否有帮助: https://dev59.com/k2Yq5IYBdhLWcg3wwDNA - Georg Heiler
2
你是否检查过 some_function(df) 是否具有创建对 df 的持久引用或以其他方式泄漏的副作用? - Steven Rumbalski
1
你的内存泄漏是由于检查内存泄漏而导致的,请参考此链接(https://stackoverflow.com/questions/26554102/memory-leak-in-adding-list-values)。假设你已经像你在这里发布的那样进行了分析(https://gist.github.com/geoHeil/ae3c235595ff3adb3ad73407eab5ad53)。 - DJK
确认。没错。 - Georg Heiler
实际代码仍然出了更多的问题。但是对于最小样本和大约一半的内存泄漏,您发布的内容是正确的。请创建一个答案。 - Georg Heiler
显示剩余4条评论
3个回答

1
问题出在作用域上。当你在循环中创建一个新对象时,它应该在循环结束后仍然可以访问。这就是为什么(我猜),垃圾收集器不会标记使用 copy 创建的对象进行垃圾回收。当你在函数内部创建新对象时,这些对象仅限于函数作用域,不能在函数外部使用。这就是为什么它们会被回收。
你提到将 foo_thing = None 赋值可以解决问题。这样做是因为通过将 foo_thing 指向另一个对象(None),就不再有变量指向数据帧了。我使用类似的方法,但不是 foo_thing = None,而是 del foo_thing。毕竟,显式优于隐式

0

我建议您不要复制代码,而是直接在groupby上进行迭代。这样可以解决你的问题吗?

for month, df in original_df.groupby('month'):
    result = some_function(df)
    print(result)

不是pandas引起了问题,而是仪器造成了问题。 - Georg Heiler

0

我使用了最小的示例并稍作修改,使用Pympler中的tracker在执行一组循环后查看差异,但即使经过10,000次循环,我也看不到任何内存泄漏。

这是使用Python 3.6.0、Numpy 1.13.1和Pandas 0.20.3进行测试的。

因此,要么您提供的最小示例无法复制该问题,要么该问题与版本有关。

import pandas as pd
from numpy.random import randn
from pympler import tracker
from tqdm import tqdm_notebook


df = pd.DataFrame(randn(10000,3),columns=list('ABC'))

tr_initial = tracker.SummaryTracker()

for i in tqdm_notebook(range(10000)):
    something = df.copy()

tr_initial.print_diff()  

输出:

                                                 types |   # objects |   total size
====================================================== | =========== | ============
                                          <class 'dict |          78 |     28.73 KB
                                          <class 'list |          36 |      4.59 KB
                <class 'traitlets.config.loader.Config |          17 |      4.25 KB
                                         <class 'bytes |          22 |      2.65 KB
                                           <class 'str |           9 |    771     B
                                          <class 'cell |          15 |    720     B
                                         <class 'tuple |          11 |    704     B
                                   function (<lambda>) |           4 |    544     B
                                        <class 'method |           7 |    448     B
                                          <class 'code |           3 |    432     B
                      <class 'ipykernel.comm.comm.Comm |           7 |    392     B
  <class 'ipywidgets.widgets.widget.CallbackDispatcher |           3 |    168     B
       <class 'ipywidgets.widgets.widget_layout.Layout |           3 |    168     B
                                 function (store_info) |           1 |    136     B
                               function (null_wrapper) |           1 |    136     B

这是我的系统日志:https://gist.github.com/geoHeil/ae3c235595ff3adb3ad73407eab5ad53 - Georg Heiler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接