Pandas 获取数据帧的一小部分时,不会释放内存

Pandas 获取数据帧的一小部分时,不会释放内存,pandas,Pandas,摘要 adataframe是一个具有800k行的DataFrame。当然,它会消耗一点内存。当我这样做时: adataframe = adataframe.tail(144) 内存没有释放 您可能会争辩说它已发布,但似乎已被使用,但它被标记为免费,将被Python重用。但是,如果我尝试创建一个新的800k行数据帧,并且只保留一个小片段,那么内存使用就会增加。如果我再做一次,它就会无限地再次生长 我正在使用Debian Jessie的Python 3.4.2和Pandas 0.18.1和nump

摘要

adataframe
是一个具有800k行的
DataFrame
。当然,它会消耗一点内存。当我这样做时:

adataframe = adataframe.tail(144)
内存没有释放

您可能会争辩说它已发布,但似乎已被使用,但它被标记为免费,将被Python重用。但是,如果我尝试创建一个新的800k行
数据帧
,并且只保留一个小片段,那么内存使用就会增加。如果我再做一次,它就会无限地再次生长

我正在使用Debian Jessie的Python 3.4.2和Pandas 0.18.1和numpy 1.11.1

用最少的程序演示

使用下面的程序,我创建了一个字典

data = {
    0:  a_DataFrame_loaded_from_a_CSV,_only_the_last_144_rows,
    1:  same_thing,
    # ...
    9: same_thing,
}
在创建字典时,我会监控内存使用情况。这是:

#!/usr/bin/env python3

from resource import getrusage, RUSAGE_SELF

import pandas as pd


def print_memory_usage():
    print(getrusage(RUSAGE_SELF).ru_maxrss)


def read_dataframe_from_csv(f):
    result = pd.read_csv(f, parse_dates=[0],
                        names=('date', 'value', 'flags'),
                        usecols=('date', 'value', 'flags'),
                        index_col=0, header=None,
                        converters={'flags': lambda x: x})
    result = result.tail(144)
    return result


print_memory_usage()
data = {}
for i in range(10):
    with open('data.csv') as f:
        data[i] = read_dataframe_from_csv(f)
    print_memory_usage()
结果

如果
data.csv
仅包含几行(例如144行,在这种情况下,切片是冗余的),内存使用量增长非常缓慢。但如果
data.csv
包含800k行,则结果与以下类似:

52968
153388
178972
199760
225312
244620
263656
288300
309436
330568
349660
(在
print\u memory\u usage()
之前添加
gc.collect()
,不会产生任何显著差异。)

我能做些什么?

您可能会争辩说它已发布,但似乎已被使用,但它被标记为免费,将被Python重用

请更正
maxrss
的工作原理(它测量内存使用峰值)。看

因此,接下来的问题是,为什么垃圾收集器在原始数据帧被子集后不清理它们

我怀疑这是因为子集将返回一个充当原始数据帧代理的数据帧(因此不需要复制值)。这将导致相对较快的子集操作,但也会导致内存泄漏,就像您在设置值时发现的那样

您可能会争辩说它已发布,但似乎已被使用,但它被标记为免费,将被Python重用

请更正
maxrss
的工作原理(它测量内存使用峰值)。看

因此,接下来的问题是,为什么垃圾收集器在原始数据帧被子集后不清理它们


我怀疑这是因为子集将返回一个充当原始数据帧代理的数据帧(因此不需要复制值)。这将导致相对快速的子集操作,但也会导致内存泄漏,就像您在设置值时发现的那样。

正如@Alex所指出的,对数据帧进行切片只能查看原始帧,但不会删除它;为此,您需要使用
.copy()
。然而,即使在我使用
.copy()
时,内存使用量也在不断增长,尽管速度较慢

我怀疑这与
Python
numpy
pandas
如何使用内存有关。数据帧不是内存中的单个对象;它包含指向其他对象的指针(特别是在本例中,指向字符串,即“flags”列)。当数据帧被释放,并且这些对象被释放时,回收的可用内存空间可以被碎片化。稍后,当创建一个巨大的新数据帧时,它可能无法使用碎片空间,可能需要分配新的空间。细节取决于许多小东西,例如
Python
numpy
pandas
版本,以及每个案例的细节


我没有研究这些小细节,而是决定阅读一个庞大的时间序列,然后对其进行切片是不可能的,而且我必须从一开始就只阅读我需要的部分。我喜欢我为此创建的一些代码,即模块和类。

正如@Alex所指出的,对数据帧进行切片只能查看原始帧,但不能删除它;为此,您需要使用
.copy()
。然而,即使在我使用
.copy()
时,内存使用量也在不断增长,尽管速度较慢

我怀疑这与
Python
numpy
pandas
如何使用内存有关。数据帧不是内存中的单个对象;它包含指向其他对象的指针(特别是在本例中,指向字符串,即“flags”列)。当数据帧被释放,并且这些对象被释放时,回收的可用内存空间可以被碎片化。稍后,当创建一个巨大的新数据帧时,它可能无法使用碎片空间,可能需要分配新的空间。细节取决于许多小东西,例如
Python
numpy
pandas
版本,以及每个案例的细节


我没有研究这些小细节,而是决定阅读一个庞大的时间序列,然后对其进行切片是不可能的,而且我必须从一开始就只阅读我需要的部分。我喜欢为此创建的一些代码,即模块和类。

事实上,如果我将
result=result.tail(144)
更改为
result=result.tail(144).copy();gc.collect()
,经过10次迭代后,它的消耗量减少了约1亿。然而,它仍然在不断地增长,尽管增长速度较低。事实上,如果我将
result=result.tail(144)
更改为
result=result.tail(144.copy();gc.collect()
,经过10次迭代后,它的消耗量减少了约1亿。然而,尽管增长率较低,它仍然在不断增长。