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

我有一个非常大的 csv 文件,我打开在熊猫如下..。

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

一旦我这样做,我的内存使用增加了2 GB,这是预期的,因为这个文件包含数百万行。当我需要释放这段记忆时,问题就来了。我跑了。

del df

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

202336 次浏览

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

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

使用 Obgragh检查哪些对象持有对象。

正如评论中提到的,有一些事情可以尝试: 例如,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]

然后是 该函数在另一个进程中执行。当这个过程完成时,操作系统将重新获取它使用的所有资源。巨蟒,熊猫,垃圾收集者,根本无法阻止这一切。

在 Python 中减少内存使用是困难的,因为 Python 实际上并没有将内存释放回操作系统。如果删除了对象,那么内存对于新的 Python 对象是可用的,但是 free()不能返回到系统(看到这个问题)。

如果您坚持使用数字数组,那么这些数组将被释放,但是装箱的对象将不会被释放。

>>> 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 与 ipython --cache-size=5保持的历史记录(默认为1000)。

减小数据帧大小

尽可能避免使用对象 dtype。

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

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

Numpy 在数组中支持固定大小的字符串,而熊猫不支持(导致了用户的困惑)。这可能会产生显著的差异:

>>> 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 非常常见) ,那么您可以使用 稀疏数据结构稀疏数据结构来减少内存使用:

>>> 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

查看内存使用情况

您可以查看内存使用情况(医生) :

>>> 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

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

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

import gc
import pandas as pd


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

数据帧将被显式设置为 null

在上述陈述中

首先,数据帧的自引用被删除,这意味着当所有数据帧的引用被垃圾收集器(gc.Collection ())收集并显式地将所有引用设置为空数据帧后,那里的 python 将不再可用该数据帧。

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

看起来 glibc 有个问题影响了熊猫的内存分配: https://github.com/pandas-dev/pandas/issues/2659

猴子补丁详细介绍了这个问题为我解决了这个问题:

# 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)

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

我有一个小应用程序,读入大型数据集熊猫数据框架,并作为一个应用程序接口服务器。然后,用户可以通过将查询参数传递到 api 来查询数据框架。当用户读入多个数据集时,显然应用程序面临内存使用限制。

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

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

前端提供了一个 api 来清除字典。这将调用 dictionary 的 clear ()方法。这可以定制为在 sys.getsizeof (df _ file _ content)具有一定大小或可用于删除某些键时调用。

df_file_contents.clear()