numpy 了解pickle对象大小

wqsoz72f  于 2023-10-19  发布在  其他
关注(0)|答案(2)|浏览(135)

我在Python解释器中执行以下代码:

len(pickle.dumps([numpy.random.random(384).tolist()]*55))

它提供3584。
假设numpy.random.random产生8字节浮点数,则被pickle的数据为384 * 8 * 55 = 168,960字节。为什么len给出这么小的数字?

xpcnnkqh

xpcnnkqh1#

当你乘一个列表的列表时,会添加一个内部列表的浅副本。
举例来说:

a = [[7, 8, 9]] * 5
id(a[0]) == id(a[1])  # True

换句话说,a等于:

reference_to_inner_list = [7, 8, 9]
a = [
    reference_to_inner_list,
    reference_to_inner_list,
    reference_to_inner_list,
    reference_to_inner_list,
    reference_to_inner_list,
]

pickle按原样序列化这个对象,所以序列化的对象也是一个引用列表。
幸运的是,我们有一个人类可读的协议,实际上可视化这一点。

pickle.dumps([[7,8,9]], protocol=0)
pickle.dumps([[7,8,9]]*5, protocol=0)

输出:

b'(lp0\n(lp1\nI7\naI8\naI9\naa.'
b'(lp0\n(lp1\nI7\naI8\naI9\naag1\nag1\nag1\nag1\na.'
               ^    ^    ^     ^    ^    ^    ^
               7    8    9    r-2  r-3  r-4  r-5

其中r-2,...、r-5分别是第二至第五个参考。正如您所看到的,与实际列表相比,引用非常小。
这也是为什么你的目标如此之小的原因。你的对象由384个浮点数和54个对该列表的引用组成。
如果对象是一个平面列表,那么它的大小将会爆炸,因为它不再是一个引用。

len(pickle.dumps(numpy.random.random(384).tolist()*55))
# 190156

这种行为对于pickle来说是必不可少的,例如在处理树结构数据时。如果pickle不能将一个引用转换为一个引用,那么树结构将被破坏。

编辑:

对于有问题的对象,不执行压缩,因为它们是浮点数。例如,如下所示,浮点数组的大小大约是元素数x 9字节(我猜8字节用于数据,1字节用于追加操作)。

len(pickle.dumps(numpy.zeros(384*55).tolist()))
# 190156
384*55*9
# 190080

这表明没有对这个对象执行压缩,但这并不意味着pickle根本没有进行压缩。例如,如果值是整数,如下所示,它会更小。

len(pickle.dumps(numpy.zeros(384*55, dtype=numpy.int64).tolist()))
# 42298
384*55*2
# 42240

所以,pickle做了某种压缩(或编码),但不是在这种情况下。

yhuiod9q

yhuiod9q2#

你创建了一个嵌套列表,而一个包含384个0到1之间的随机浮点数的列表有55个副本。这可能不是您所期望的,但您可以在此处看到输出

import numpy as np
res = [np.random.random(384).tolist()]*55
print(res)

如果你想做整数列表,你会想用np.random.randint(0,9)代替。
至于为什么pickle对象的具体大小,那就稍微复杂一点,并不是直接根据字节大小来计算的。这是pickle数据格式的结果,它不直接将数据存储为字符串,而是使用一组python特定的操作码来分解python对象,并以压缩的方式将它们存储为一系列字节。unpickle进程读取这些操作码以将对象重建回其原始状态。
如果你以字节格式查看pickle对象,你不会看到任何与列表中的值相似的内容,因为所有内容都是用它自己的内部语言编码的。
你不会得到一个字节计数,因为它是一个压缩格式,是一个答案。如果你想弄清楚对象的确切大小,你必须深入研究文件中使用的每个操作码的存储大小,并从那里建立存储消耗,然后将其存储为字节。
参考文献:
Pickle操作码
Source -编码发生的地方
Pickler类对象

相关问题