在获取一小部分DataFrame时,不会释放内存

时间:2016-07-03 14:30:03

标签: pandas

摘要

adataframeDataFrame,行数为800k。当然,它消耗了一点内存。当我这样做时:

adataframe = adataframe.tail(144)

内存未发布。

你可能会认为已经发布,但它似乎已被使用,但它被标记为免费并且将被Python重用。但是,如果我尝试创建一个新的800k行DataFrame并且只保留一小片,则内存使用量会增加。如果我再次这样做,它会再次增长,无限期。

我使用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

(在gc.collect()之前添加print_memory_usage()没有任何显着差异。)

我该怎么办?

2 个答案:

答案 0 :(得分:1)

  

你可以说它已经发布了,但它似乎被使用了,但是它被标记为免费并且将被Python重用。

纠正maxrss的工作原理(它测量峰值内存使用情况)。请参阅1

那么问题就是为什么垃圾收集器在子集化之后不会清理原始的DataFrame。

我怀疑这是因为subsetting返回一个DataFrame作为原始代理的代理(因此不需要复制值)。这将导致相对快速的子集操作,但也会导致内存泄漏,例如您在设置值时发现的内容和here

答案 1 :(得分:1)

正如@Alex所指出的那样,切片数据帧只会给你原始帧的视图,但不会删除它;您需要使用.copy()。但是,即使我使用.copy(),内存使用量也会增长,增长和增长,尽管速度较慢。

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

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