Pandas使用的存储内存比所需的要多得多

12

我正在使用numpy (1.13.1)和pandas (0.20.3),在Ubuntu 16.10上使用python 2.7或3.5(无论哪种都存在同样的问题)。

我正在研究pandas的内存处理(特别是当它复制或不复制数据时),并遇到了一个我不理解的重大内存问题。虽然我看过(很多)其他人关于其内存性能的问题,但我没有找到直接解决这个问题的任何问题。

具体来说,pandas分配的内存比我要求的要多得多。当我尝试仅分配具有特定大小列的DataFrame时,我注意到了一些非常奇怪的行为:

import pandas as pd, numpy as np
GB = 1024**3
df = pd.DataFrame()
df['MyCol'] = np.ones(int(1*GB/8), dtype='float64')
当我执行这段代码时,我发现我的 Python 进程实际上分配了 6GB 的内存(如果我要求 2GB,则为 12GB;如果我要求 3GB,则为 21GB;如果我要求 4GB,则我的计算机会崩溃 :-/),而不是预期的 1GB。起初我以为是 Python 在进行一些积极的预分配,但是如果我只构造 numpy 数组本身,每次得到的内存大小都恰好是我所请求的大小,无论是 1GB、10GB 还是 25GB 等。

此外,更有趣的是,如果我稍微改变一下代码:

df['MyCol'] = np.ones(int(1*GB), dtype='uint8')

它分配了如此之多的内存,以至于导致我的系统崩溃(仅运行numpy调用就可以正确分配1GB的内存)。(编辑于2017/8/17:出于好奇,我今天尝试使用更新的pandas(0.20.3)和numpy(1.13.1)版本,以及升级到64GB的RAM。但运行此命令仍然有问题,分配了所有64(左右)GB可用内存。)

如果pandas复制数据并分配另一列来存储索引,则可以理解申请的内存加倍甚至三倍,但我无法解释实际上正在发生的事情。从浏览代码中也不太清楚。

我尝试以几种不同的方式构建数据框架,但都得到了相同的结果。考虑到其他人成功地使用此包进行大型数据分析,我不得不认为我做错了什么,尽管从文档中可以看出这是正确的。

你有什么想法吗?

一些额外的说明:

  1. 即使内存使用量很大,当调用memory_usage()时,pandas仍然(错误地)报告预期的数据大小(即,如果我分配一个1GB数组,它报告1GB,即使实际分配了6-10GB)。
  2. 在所有情况下,索引都很小(如memory_usage()所报告的那样,可能不准确)。
  3. 释放pandas数据框(df = None,gc.collect())并不能实际上释放所有内存。这种方法肯定存在泄漏。

2
试试这个:df = pd.DataFrame({'MyCol':np.ones(int(1*GB), dtype=np.uint8)}) - MaxU - stand with Ukraine
2
@MaxU 虽然这个方法做得更好了,但仍然不完美。有一个很大的开销(对象大小的约100%,稍微多一点)被分配了(我认为这是复制数据)。这似乎比我的示例中发生的任何事情都更有合理的解释,但如果我需要在内存中抛出超过15GB的数据,则无济于事。(注意:即使数据分布在多个通道上,例如3个5GB通道,使用此方法也需要几乎30GB来分配,因此如果正在进行数据复制,则没有一个被释放直到最后。) - Anthony
1
df.index 占用多少空间?如果添加另一列,可能是相同的 np.array,内存使用会如何变化? - hpaulj
1
@hpaulj 在使用MaxU的方法构建后,索引占用了72个字节。最终的DataFrame是正确的大小(15GB),只是为了达到这个目的分配了很多开销。如果我使用上面的方法分配1GB的双精度浮点数,则通过df.memory_usage()报告的大小再次为索引占用了72个字节,数据占用了1GB,尽管在这种情况下,实际分配了约10GB的内存。 - Anthony
1个回答

1

所以我创建了一个8000字节的数组:

In [248]: x=np.ones(1000)

In [249]: df=pd.DataFrame({'MyCol': x}, dtype=float)
In [250]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000 entries, 0 to 999
Data columns (total 1 columns):
MyCol    1000 non-null float64
dtypes: float64(1)
memory usage: 15.6 KB

所以8k是数据,另外8k是索引。
我添加了一列 - 使用量增加了 x 的大小:
In [251]: df['col2']=x
In [252]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000 entries, 0 to 999
Data columns (total 2 columns):
MyCol    1000 non-null float64
col2     1000 non-null float64
dtypes: float64(2)
memory usage: 23.4 KB

In [253]: x.nbytes
Out[253]: 8000

我在我的问题中更新了一些注释。即使实际分配了更多内存(例如6GB),Pandas报告了预期的内存利用率(例如,如果我创建了一个1GB的数组,则报告1GB)。它也没有在对象被销毁并运行垃圾收集器时释放所有已分配的内存,这让我相信Pandas可能存在内存泄漏问题。 - Anthony
所以你更关心沿途的内存使用情况,而不是最终使用情况。我想知道如果你创建一个numpy数组,然后复制加上相同大小的np.arange()(索引数组),会发生什么。换句话说,尝试创建相同的最终numpy数组,但没有df结构。 - hpaulj
无论如何,这是“最终”使用方式。DataFrame 可能只有1GB,但如果分配的总内存(仍保持分配状态,而不是在构造对象过程中使用的内存)大6倍,那就是一个真正的问题。此外,请注意,在使用uint8初始化的情况下,分配1GB内存实际上会消耗我的内存(32GB)并导致崩溃。这些都是问题。正如帖子中提到的那样,构造numpy数组本身并不会造成问题,只有将其放入DataFrame中才会出现问题。 - Anthony
我怀疑pandas至少复制了一个,如果不是两个numpy数组。还有索引数组。如果它扫描输入以获得统一性或其他需要进行索引和dtype决策的内容,则可能会有一个或多个临时数组。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接