Pandas 的内存使用情况与内联 numpy 不一致

问题描述 投票:0回答:1

有人可以帮忙解释一下为什么这里的结果不同吗?

特别是序列化/反序列化后输出的内存使用情况有很大不同。

我唯一的线索是

df["data"][0].flags
输出 'OWNDATA` 与后者不同。

import pandas as pd
import numpy as np
import pickle

df = pd.DataFrame({
    "data": [np.random.randint(size=1024, low=0, high=100, dtype=np.int8) for _ in range(1_000_000)]
})

print(df["data"].size, df["data"].dtype, df.memory_usage(index=True, deep=True).sum())
# 1000000 object 1144000132
df2 = pickle.loads(pickle.dumps(df))
print(df2["data"].size, df2["data"].dtype, df2.memory_usage(index=True, deep=True).sum())
# 1000000 object 120000132

print(np.array_equal(df["data"][0], df2["data"][0])) 
python pandas dataframe numpy
1个回答
0
投票

我的评论越来越长,所以我把它们放在这里作为答案。

底线是:这是一个 numpy 问题,而不是 pandas 问题。从pandas的角度来看,这里有一个1000000行和1列的数组,包含1000000个“对象”,即不透明的外部指针,pandas对此一无所知,只有它能够存储它们,如果您需要的话,并把它们还给您。

这就是为什么,如果你只是问

df.memory_usage()
,你前后会得到完全相同的答案。 因为,从熊猫的角度来看,这才是最重要的。这就是 pandas 数据帧中存储的内容,使用 120000132 字节(132 用于索引,1000000 × 120,即每个指向外部未知数据的“指针”120 字节。120 是“指针”的大小)

但是你添加了

deep=True
。文档说,如果您这样做,pandas 将询问对象的系统级内存使用情况。

它的作用

import sys
arr1=df.data[0]
arr2=df2.data[0]
print(sys.getsizeof(arr1)  # 1136
print(sys.getsizeof(arr2) # 112

所以,你得到了什么(两个指针都有 8 个字节的开销,因此引号 hitheto = 120 指针的大小,加上无数据 numpy 数组的大小)

注意

df.data[0]
df2.data[0]
只是 numpy 数组,与 pandas 无关。因此我使用中间变量来摆脱 pandas。一旦我将它们放入 arr1 和 arr2 中,我就不会使用任何 pandas 代码来完成其余的工作。

所以,问题是为什么 arr1 和 arr2 显然是相同的数组(

(arr1==arr2).all()
是 True),具有不同的大小,来自
sys.getsizeof

您给出了答案:

arr1.flags
包含
OWNDATA
arr2.flags
不包含。

arr1 是一个普通数组(您自己使用

np.random.randint
创建)。

同时,正如 tdelaney 在评论中所说,arr2 是由 pickle 从字节字符串制成的。 你可以看到

arr1.base # None
arr2.base # b'stuf'

这一点也不奇怪。您希望 pickle 从文件中读取字节,然后从这些字节构建 numpy 数组

只是发生的事情的模拟

arr1=np.arange(4, dtype=np.uint8)
arr2=np.frombuffer(b'\x00\x01\x02\x03', dtype=np.uint8)
arr1, arr2 # both array([0,1,2,3])
arr1.base, arr2.base # None and b'\x00\x01\x02\x03'
arr1.flags.owndata, arr2.flags.owndata # True, False
# Hence
sys.getsizeof(arr1), sys.getsizeof(arr2) # 116, 112 : 4 bytes of data counted in arr1, but not arr2 (since they belong to the byte string that still exist as a python object in memory)
# Hence, to be complete — but again, pandas has nothing to do with that
# Just, its "deep" computation of memory usage reflect the size of the
# objects it stores. And arr1 and arr2 doesn't have the same sizes
# from python point of view.
df1=pd.DataFrame({"data":[arr1]})
df2=pd.DataFrame({"data":[arr2]})
df1.memory_usage(deep=True).sum(), df2.memory_usage(deep=True).sum()
# 256, 252 : still the 4 bytes of data, counted in the "deep" size of 
# opaque object that points to arr1 (because getsizeof(arr1) counts it)
# and not in the one of arr2

所以,tl;博士

np.frombuffer(bytestring)

不拥有其数据。字节串是另一个Python对象。 同时

np.random.randint(...)

是(numpy数组不涉及其他Python对象)

这使得这两个 numpy 数组的 python 系统大小不同。这反映了您通过

df.memory_usage
和选项
deep=True

得到的结果
© www.soinside.com 2019 - 2024. All rights reserved.