如何释放熊猫数据框使用的内存?
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()
【讨论】:
以上是关于如何释放熊猫数据框使用的内存?的主要内容,如果未能解决你的问题,请参考以下文章