如何释放熊猫数据框使用的内存?

Posted

技术标签:

【中文标题】如何释放熊猫数据框使用的内存?【英文标题】:How do I release memory used by a pandas dataframe? 【发布时间】:2016-12-30 06:29:20 【问题描述】:

我有一个非常大的 csv 文件,我在 pandas 中打开如下......

import pandas
df = pandas.read_csv('large_txt_file.txt')

执行此操作后,我的内存使用量会增加 2GB,这是意料之中的,因为该文件包含数百万行。当我需要释放此内存时,我的问题就来了。我跑了....

del df

但是,我的内存使用量并没有下降。这是释放熊猫数据框使用的内存的错误方法吗?如果是,正确的方法是什么?

【问题讨论】:

没错,垃圾回收器可能不会马上释放内存,你也可以导入gc模块并调用gc.collect(),但它可能无法回收内存 del df 不会在创建df后直接调用吧?我认为在您删除 df 时有对 df 的引用。所以它不会被删除,而是删除名称。 垃圾收集器回收的内存是否实际返回给操作系统取决于实现;垃圾收集器所做的唯一保证是,当前 Python 进程可以将回收的内存用于其他事情,而不是向操作系统请求甚至 更多 内存。 我在创建后立即调用 del df。我没有添加任何其他对 df 的引用。我所做的只是打开 ipython 并运行这三行代码。如果我在其他需要大量内存的对象上运行相同的代码,比如一个 numpy 数组。 del nparray 完美运行 @b10hazard :代码末尾的df = '' 怎么样?似乎清除了数据框使用的 RAM。 【参考方案1】:

在 Python 中减少内存使用很困难,因为Python does not actually release memory back to the operating system。如果删除对象,则内存可用于新的 Python 对象,但free()'d 不能返回系统 (see this question)。

如果你坚持使用数字 numpy 数组,它们会被释放,但装箱的对象不会。

>>> import os, psutil, numpy as np # psutil may need to be installed
>>> def usage():
...     process = psutil.Process(os.getpid())
...     return process.memory_info()[0] / float(2 ** 20)
... 
>>> usage() # initial memory usage
27.5 

>>> arr = np.arange(10 ** 8) # create a large array without boxing
>>> usage()
790.46875
>>> del arr
>>> usage()
27.52734375 # numpy just free()'d the array

>>> arr = np.arange(10 ** 8, dtype='O') # create lots of objects
>>> usage()
3135.109375
>>> del arr
>>> usage()
2372.16796875  # numpy frees the array, but python keeps the heap big

减少数据帧的数量

Python 将我们的内存保持在高水位,但我们可以减少我们创建的数据帧的总数。修改数据框时,首选inplace=True,这样您就不会创建副本。

另一个常见的问题是保留以前在 ipython 中创建的数据帧的副本:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame('foo': [1,2,3,4])

In [3]: df + 1
Out[3]: 
   foo
0    2
1    3
2    4
3    5

In [4]: df + 2
Out[4]: 
   foo
0    3
1    4
2    5
3    6

In [5]: Out # Still has all our temporary DataFrame objects!
Out[5]: 
3:    foo
 0    2
 1    3
 2    4
 3    5, 4:    foo
 0    3
 1    4
 2    5
 3    6

您可以通过键入 %reset Out 来清除您的历史记录来解决此问题。或者,您可以使用ipython --cache-size=5 调整 ipython 保留多少历史记录(默认为 1000)。

减少数据框大小

尽可能避免使用对象数据类型。

>>> df.dtypes
foo    float64 # 8 bytes per value
bar      int64 # 8 bytes per value
baz     object # at least 48 bytes per value, often more

具有 object dtype 的值被装箱,这意味着 numpy 数组只包含一个指针,并且对于数据帧中的每个值,您在堆上都有一个完整的 Python 对象。这包括字符串。

虽然 numpy 支持数组中固定大小的字符串,但 pandas 不支持 (it's caused user confusion)。这可能会产生重大影响:

>>> import numpy as np
>>> arr = np.array(['foo', 'bar', 'baz'])
>>> arr.dtype
dtype('S3')
>>> arr.nbytes
9

>>> import sys; import pandas as pd
>>> s = pd.Series(['foo', 'bar', 'baz'])
dtype('O')
>>> sum(sys.getsizeof(x) for x in s)
120

您可能希望避免使用字符串列,或者想办法将字符串数据表示为数字。

如果你有一个包含许多重复值的数据框(NaN 很常见),那么你可以使用sparse data structure 来减少内存使用:

>>> df1.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 605.5 MB

>>> df1.shape
(39681584, 1)

>>> df1.foo.isnull().sum() * 100. / len(df1)
20.628483479893344 # so 20% of values are NaN

>>> df1.to_sparse().info()
<class 'pandas.sparse.frame.SparseDataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 543.0 MB

查看内存使用情况

可以查看内存使用情况(docs):

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 14 columns):
...
dtypes: datetime64[ns](1), float64(8), int64(1), object(4)
memory usage: 4.4+ GB

从 pandas 0.17.1 开始,您还可以通过 df.info(memory_usage='deep') 查看包括对象在内的内存使用情况。

【讨论】:

这必须标记为“已接受的答案”。它简要但清楚地解释了 python 如何在它并不真正需要它的情况下保持内存。节省内存的技巧都是明智和有用的。作为另一个提示,我将使用“多处理”添加(如@Ami 的回答中所述。 “尽可能使用inplace=True”。 不,这是一个神话! 请参阅this answer 了解原因。 (否则,总体而言,答案很好。)【参考方案2】:

正如 cmets 中所述,有一些事情可以尝试:例如,gc.collect (@EdChum) 可能会清除一些东西。至少从我的经验来看,这些东西有时会奏效,但通常不会。

但是,有一件事总是有效的,因为它是在操作系统而不是语言级别上完成的。

假设您有一个函数创建一个中间巨大的 DataFrame,并返回一个较小的结果(也可能是一个 DataFrame):

def huge_intermediate_calc(something):
    ...
    huge_df = pd.DataFrame(...)
    ...
    return some_aggregate

如果你做类似的事情

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]

然后the function is executed at a different process。当该过程完成时,操作系统会重新占用它使用的所有资源。 Python、pandas、垃圾收集器真的无法阻止这种情况。

【讨论】:

@b10hazard 即使没有 pandas,我也从未完全理解 Python 内存在实践中是如何工作的。这种粗糙的技术是我唯一依赖的东西。 效果很好。然而,在 ipython 环境(如 jupyter notebook)中,我发现您需要 .close() 和 .join() 或 .terminate() 池来摆脱产生的进程。自 Python 3.3 以来,最简单的方法是使用上下文管理协议:with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something]) 完成后关闭池。 这很好,只是不要忘记在任务完成后终止并加入池。 在多次阅读如何从 python 对象中收回内存之后,这似乎是最好的方法。创建一个进程,当该进程被杀死时,操作系统会释放内存。 也许它对某人有帮助,在创建池时尝试使用 maxtasksperchild = 1 以释放进程并在工作完成后生成一个新进程。【参考方案3】:

这样就解决了我释放内存的问题!!!

import gc
import pandas as pd

del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()

数据框将被显式设置为空

在上述陈述中

首先,数据帧的自引用被删除,这意味着在垃圾收集器(gc.collect())收集数据帧的所有引用之后,数据帧不再可供python使用,然后将所有引用显式设置为空数据框。

更多关于垃圾收集器的工作在https://stackify.com/python-garbage-collection/中有很好的解释

【讨论】:

为什么要在子列表 [[df_1,df_2]] 中添加数据框?有什么具体原因吗?请解释一下。 为什么不直接使用最后两个语句?我认为您不需要前两个语句。 @spacedustpi 因为只使用最后两个语句是行不通的。【参考方案4】:

如果在删除时对df 有任何引用,则不会删除del df。所以你需要用del df删除所有对它的引用来释放内存。

所以所有绑定到 df 的实例都应该被删除以触发垃圾回收。

使用objgragh 来检查是哪个持有对象。

【讨论】:

链接指向 objgraph (mg.pov.lt/objgraph),除非有 objgragh,否则这是您的答案中的错字【参考方案5】:

glibc 似乎存在影响 Pandas 内存分配的问题:https://github.com/pandas-dev/pandas/issues/2659

monkey patch detailed on this issue 为我解决了问题:

# monkeypatches.py

# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None

__old_del = getattr(pd.DataFrame, '__del__', None)

def __new_del(self):
    if __old_del:
        __old_del(self)
    libc.malloc_trim(0)

if libc:
    print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
    pd.DataFrame.__del__ = __new_del
else:
    print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)

【讨论】:

很有可能,如果我使用最新版本的 Panda,我可能不会遇到这个问题,对吧? 我也面临同样的问题,但就我而言,我使用的是 pandas 的 drop api,我也添加了上述修复。手指交叉 @ajayramesh 链接的 github 问题已关闭“无法修复”,所以我认为 Pandas 1.0 仍然存在该问题【参考方案6】:

这是我为解决这个问题所做的工作。

我有一个小型应用程序,它将大型数据集读入 pandas 数据帧并将其用作 api。然后,用户可以查询将查询参数传递到 api 的数据框。当用户读入多个数据集时,应用程序显然面临内存使用限制。

不要将数据集读入单个数据帧变量,而是将它们读入数据帧字典。

df_file_contents[file_name] = pd.read_csv(..)

已经为前端提供了一个api来清除字典。这调用了字典的 clear() 方法。这可以自定义为在 sys.getsizeof(df_file_contents) 为一定大小时调用,也可以用于删除某些键。

df_file_contents.clear()

【讨论】:

以上是关于如何释放熊猫数据框使用的内存?的主要内容,如果未能解决你的问题,请参考以下文章

熊猫记忆释放

如何使用熊猫拆分数据框?

如何使用熊猫数据框获取 tfidf?

如何使用索引属性转换时间序列熊猫数据框?

如何使用熊猫从嵌套字典创建数据框?

如何在熊猫数据框中使用列表作为值?