如何释放pandas数据帧使用的内存?

b10*_*ard 82 python memory pandas

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

import pandas
df = pandas.read_csv('large_txt_file.txt')
Run Code Online (Sandbox Code Playgroud)

一旦我这样做,我的内存使用量增加2GB,这是预期的,因为该文件包含数百万行.当我需要释放这个内存时,我的问题出现了.我跑了....

del df
Run Code Online (Sandbox Code Playgroud)

但是,我的内存使用率没有下降.这是释放熊猫数据帧所使用的内存的错误方法吗?如果是,那么正确的方法是什么?

Wil*_*hes 85

减少Python中的内存使用很困难,因为Python实际上并没有将内存释放回操作系统.如果删除对象,则内存可用于新的Python对象,但不能free()返回系统(请参阅此问题).

如果您坚持使用数字numpy数组,那么这些数组将被释放,但盒装对象则不会被释放.

>>> import os, psutil, numpy as np
>>> def usage():
...     process = psutil.Process(os.getpid())
...     return process.get_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
Run Code Online (Sandbox Code Playgroud)

减少数据帧的数量

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}
Run Code Online (Sandbox Code Playgroud)

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

减少数据帧大小

尽可能避免使用对象dtypes.

>>> df.dtypes
foo    float64 # 8 bytes per value
bar      int64 # 8 bytes per value
baz     object # at least 48 bytes per value, often more
Run Code Online (Sandbox Code Playgroud)

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

虽然numpy支持数组中固定大小的字符串,但pandas却不支持(这会导致用户混淆).这可以产生重大影响:

>>> 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
Run Code Online (Sandbox Code Playgroud)

您可能希望避免使用字符串列,或者找到将字符串数据表示为数字的方法.

如果您的数据框包含许多重复值(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
Run Code Online (Sandbox Code Playgroud)

查看内存使用情况

您可以查看内存使用情况(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
Run Code Online (Sandbox Code Playgroud)

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

  • “如果可能,请使用`inplace=True`”。**不,这是一个神话!** 请参阅[此答案](/sf/answers/4201426911/)了解原因。(否则,总体来说答案很好。) (7认同)
  • 这必须标记为“已接受的答案”。它简短而清晰地解释了 python 如何保留内存,即使它并不真正需要它。节省内存的技巧都是明智且有用的。作为另一个提示,我只是添加使用“多处理”(如 @Ami 的答案中所解释的)。 (3认同)

Ami*_*ory 29

正如评论中所指出的,有一些事情要尝试:gc.collect(@ EdChum)可能会清除一些东西,例如.至少根据我的经验,这些东西有时是有效的,而且往往不起作用.

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

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

def huge_intermediate_calc(something):
    ...
    huge_df = pd.DataFrame(...)
    ...
    return some_aggregate
Run Code Online (Sandbox Code Playgroud)

然后,如果你做的事情

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]
Run Code Online (Sandbox Code Playgroud)

然后该函数在不同的进程中执行.该过程完成后,操作系统将重新获得其使用的所有资源.实际上没有什么Python,大熊猫,垃圾收集器可以阻止它.

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

har*_*rdi 12

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

del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()
Run Code Online (Sandbox Code Playgroud)

数据框将显式设置为null

  • 为什么不直接使用最后两个语句呢?我认为您不需要前两个陈述。 (5认同)
  • 为什么在子列表 [[df_1,df_2]] 中添加数据帧?有什么具体原因吗?请解释。 (2认同)

Mar*_*oon 6

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

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

使用objgragh检查哪个抓住了物体。


Mar*_*kNS 5

glibc 似乎存在一个影响 Pandas 内存分配的问题: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)
Run Code Online (Sandbox Code Playgroud)