Python 数据帧创建时内存使用量的增加

Python 数据帧创建时内存使用量的增加,python,pandas,memory,Python,Pandas,Memory,我有一段代码,它接收来自另一个函数的回调并创建一个列表(pd_arr)。然后使用此列表创建数据帧。最后,删除列表的列表 在使用内存探查器进行评测时,这是输出 102.632812 MiB 0.000000 MiB init() 236.765625 MiB 134.132812 MiB add_to_list() return pd.DataFrame() 394.328125 MiB 1

我有一段代码,它接收来自另一个函数的回调并创建一个列表(pd_arr)。然后使用此列表创建数据帧。最后,删除列表的列表

在使用内存探查器进行评测时,这是输出

102.632812 MiB   0.000000 MiB       init()
236.765625 MiB 134.132812 MiB           add_to_list()
                                    return pd.DataFrame()
394.328125 MiB 157.562500 MiB       pd_df = pd.DataFrame(pd_arr, columns=df_columns)
350.121094 MiB -44.207031 MiB       pd_df = pd_df.set_index(df_columns[0])
350.292969 MiB   0.171875 MiB       pd_df.memory_usage()
350.328125 MiB   0.035156 MiB       print sys.getsizeof(pd_arr), sys.getsizeof(pd_arr[0]), sys.getsizeof(pd_df), len(pd_arr)
350.328125 MiB   0.000000 MiB       del pd_arr
在检查pd_df(数据帧)的深度内存使用情况时,它是80.5 MB。所以,我的问题是,为什么在
del pd_arr
行之后内存没有减少

此外,根据探查器(157-44=110MB)计算的总数据帧大小似乎超过80MB。那么,是什么导致了这种差异呢

另外,是否有其他内存效率高的方法来创建时间性能不太差的数据帧(在循环中接收的数据)(例如:对于大小为100MB的数据帧,增加10秒应该可以)

编辑:解释此行为的简单python脚本

Filename: py_test.py

Line #    Mem usage    Increment   Line Contents
================================================
     9    102.0 MiB      0.0 MiB   @profile
    10                             def setup():
    11                              global arr, size
    12    102.0 MiB      0.0 MiB    arr = range(1, size)
    13    131.2 MiB     29.1 MiB    arr = [x+1 for x in arr]


Filename: py_test.py

Line #    Mem usage    Increment   Line Contents
================================================
    21    131.2 MiB      0.0 MiB   @profile
    22                             def tearDown():
    23                              global arr
    24    131.2 MiB      0.0 MiB    del arr[:]
    25    131.2 MiB      0.0 MiB    del arr
    26     93.7 MiB    -37.4 MiB    gc.collect()
关于引入数据帧

Filename: py_test.py

Line #    Mem usage    Increment   Line Contents
================================================
     9    102.0 MiB      0.0 MiB   @profile
    10                             def setup():
    11                              global arr, size
    12    102.0 MiB      0.0 MiB    arr = range(1, size)
    13    132.7 MiB     30.7 MiB    arr = [x+1 for x in arr]


Filename: py_test.py

Line #    Mem usage    Increment   Line Contents
================================================
    15    132.7 MiB      0.0 MiB   @profile
    16                             def dfCreate():
    17                              global arr
    18    147.1 MiB     14.4 MiB    pd_df = pd.DataFrame(arr)
    19    147.1 MiB      0.0 MiB    return pd_df


Filename: py_test.py

Line #    Mem usage    Increment   Line Contents
================================================
    21    147.1 MiB      0.0 MiB   @profile
    22                             def tearDown():
    23                              global arr
    24                              #del arr[:]
    25    147.1 MiB      0.0 MiB    del arr
    26    147.1 MiB      0.0 MiB    gc.collect()

回答第一个问题时,当您尝试使用
del pd_arr
清除内存时,实际上不会发生这种情况,因为
DataFrame
存储到
pd_arr
的一个链接,而top scope保留了另一个链接;减少refcounter将不会收集内存,因为此内存正在使用中

您可以在
del pd\u arr
之前运行
sys.getrefcount(pd\u arr)
来检查我的假设,结果将得到
2

现在,我相信下面的代码片段与您尝试执行的操作相同:

如果尝试此代码段,您将看到内存使用情况如下所示:

Line #    Mem usage    Increment   Line Contents
================================================
    13   63.902 MiB    0.000 MiB   @profile
    14                             def to_profile():
    15  324.828 MiB  260.926 MiB       pd_arr = make_list()
    16                                 # pd_df = pd.DataFrame.from_records(pd_arr, columns=[x for x in range(0,1000)])
    17  479.094 MiB  154.266 MiB       pd_df = pd.DataFrame(pd_arr)
    18                                 # pd_df.info(memory_usage='deep')
    19  479.094 MiB    0.000 MiB       print sys.getsizeof(pd_arr), sys.getsizeof(pd_arr[0])
    20  481.055 MiB    1.961 MiB       print sys.getsizeof(pd_df), len(pd_arr)
    21  481.055 MiB    0.000 MiB       print sys.getrefcount(pd_arr)
    22  417.090 MiB  -63.965 MiB       del pd_arr
    23  323.090 MiB  -94.000 MiB       gc.collect()
试试这个例子:

@profile
def test():
    a = [x for x in range(0,100000)]
    del a


aa = test()
您将得到您所期望的:

Line #    Mem usage    Increment   Line Contents
================================================
     6   64.117 MiB    0.000 MiB   @profile
     7                             def test():
     8   65.270 MiB    1.152 MiB       a = [x for x in range(0,100000)]
     9                                 # print sys.getrefcount(a)
    10   64.133 MiB   -1.137 MiB       del a
    11   64.133 MiB    0.000 MiB       gc.collect()
另外,如果调用
sys.getrefcount(a)
,有时会在
dela
之前清除内存:

Line #    Mem usage    Increment   Line Contents
================================================
     6   63.828 MiB    0.000 MiB   @profile
     7                             def test():
     8   65.297 MiB    1.469 MiB       a = [x for x in range(0,100000)]
     9   64.230 MiB   -1.066 MiB       print sys.getrefcount(a)
    10   64.160 MiB   -0.070 MiB       del a
但是当你使用
pandas

如果打开
pandas.DataFrame
的源代码,您将看到,在使用
list
初始化
DataFrame
时,
pandas
创建新的NumPy数组并复制其内容。看看这个:

删除
pd_arr
不会释放内存,因为
pd_arr
将在
DataFrame
创建并退出函数后收集,因为它没有任何附加链接
getrefcount
前后调用可以证明这一点

从普通列表创建新的
DataFrame
,使用NumPy数组复制列表。(请查看
np.array(data,dtype=dtype,copy=copy)
和有关
array
的相应文档) 复制操作可能会影响执行时间,因为分配新内存块是一项繁重的操作

我尝试用Numpy数组来初始化新的数据帧。唯一的区别是
numpy.Array
内存开销出现的位置。比较以下两个代码段:

def make_list():  # 1
    pd_arr = []
    for i in range(0,10000):
        pd_arr.append([x for x in range(0,1000)])
    return np.array(pd_arr)

数字#1(创建数据帧不会产生内存使用开销!):

数字#2(由于复制阵列而导致超过100Mb的开销)!:

因此,仅使用Numpy数组初始化
DataFrame
,而不是
列表。从内存消耗的角度来看,它更好,而且可能更快,因为它不需要额外的内存分配调用


希望现在我已经回答了你所有的问题

您是否完全确定代码中的任何其他地方都没有引用
pd\u arr
?Python是引用计数的,因此只有在可以确保删除的对象不能从任何地方使用的情况下,使用
del
才能释放相关的内存。您也可以尝试。我尝试使用了
del pd\u arr[:]
。没有内存减少。pd_arr在代码中定义为全局。这会有所不同吗?嗯
del pd_arr
只是意味着你不能再使用名称
pd_arr
来引用该列表,无论是全局的还是非全局的,但是如果在以前的某个点上有类似
a=pd_arr
(虽然它可能是更微妙的东西,比如将
pd_arr
传递给函数并将其引用复制到其他地方),但它不会被真正删除。但是,我无法解释为什么
del pd_arr[:]
没有任何区别。没有其他指向pd_arr的引用指针。我尝试在创建数据帧之前和之后计算ref计数。在创建数据帧之前和之后,ref计数都是2。我看到了相同的情况。我正在尝试理解什么是错的。ref计数是2是可以的。>>>导入系统>>>a=[1,2,3]>>>打印系统getrefcount(a)2@Rajs123请检查我的答案!在创建数据帧期间,您必须更喜欢np.array而不是list,因为它速度更快,并且不需要额外的数据复制,这发生在
pandas
内部(答案中有证据)。非常感谢!这是我所需要的一切。此外,感谢您指出代码。现在将经常使用它:)
def make_list():  #2
    pd_arr = []
    for i in range(0,10000):
        pd_arr.append([x for x in range(0,1000)])
    return pd_arr
Line #    Mem usage    Increment   Line Contents
================================================
    14   63.672 MiB    0.000 MiB   @profile
    15                             def to_profile():
    16  385.309 MiB  321.637 MiB       pd_arr = make_list()
    17  385.309 MiB    0.000 MiB       print sys.getrefcount(pd_arr)
    18  385.316 MiB    0.008 MiB       pd_df = pd.DataFrame(pd_arr)
    19  385.316 MiB    0.000 MiB       print sys.getsizeof(pd_arr), sys.getsizeof(pd_arr[0])
    20  386.934 MiB    1.617 MiB       print sys.getsizeof(pd_df), len(pd_arr)
    21  386.934 MiB    0.000 MiB       print sys.getrefcount(pd_arr)
    22  386.934 MiB    0.000 MiB       del pd_arr
    23  305.934 MiB  -81.000 MiB       gc.collect()
Line #    Mem usage    Increment   Line Contents
================================================
    14   63.652 MiB    0.000 MiB   @profile
    15                             def to_profile():
    16  325.352 MiB  261.699 MiB       pd_arr = make_list()
    17  325.352 MiB    0.000 MiB       print sys.getrefcount(pd_arr)
    18  479.633 MiB  154.281 MiB       pd_df = pd.DataFrame(pd_arr)
    19  479.633 MiB    0.000 MiB       print sys.getsizeof(pd_arr), sys.getsizeof(pd_arr[0])
    20  481.602 MiB    1.969 MiB       print sys.getsizeof(pd_df), len(pd_arr)
    21  481.602 MiB    0.000 MiB       print sys.getrefcount(pd_arr)
    22  417.621 MiB  -63.980 MiB       del pd_arr
    23  330.621 MiB  -87.000 MiB       gc.collect()