【问题标题】:Lists and sys.getsizeof inconsistency [duplicate]列表和 sys.getsizeof 不一致 [重复]
【发布时间】:2019-09-12 01:05:19
【问题描述】:

我读过 python 列表应该占用 64 个字节,并且列表中的每个条目都应该“花费”8 个额外的字节。我决定测试一下。但是,在测试时,我发现这取决于您如何将项目添加到列表中。为什么下面代码中ob1obj2sys.getsizeof不一致?

import sys

test1 = 'This is a string.'

obj1 = []

obj2 = ['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.']

obj3 = list(test1)

for i in range(len(test1)):
    obj1.append(test1[i])

print(sys.getsizeof(obj1))
print(obj1)
print(sys.getsizeof(obj2))
print(obj2)
print(sys.getsizeof(obj3))
print(obj3)

>>>264
>>>['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.']
>>>200
>>>['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.']
>>>264
>>>['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.']

obj2 报告我预期的大小 (64+8*17=200)。使用append-function 后的开销是多少,在构造列表后能否以某种方式消除开销?

我已经阅读了这个相关主题here,但我认为他们的答案与另一个似乎与 Pandas 相关的答案不同。

【问题讨论】:

  • 因为当您使用列表文字时,Python 知道 exact 大小。如果您使用.append,它会在重新调整大小时变得过大。更重要的是,你为什么关心?这个开销是一个无穷小部分,它可以让你摊销固定时间附加,这太棒了。
  • 几乎可以肯定,这是一个副本:stackoverflow.com/questions/51526242/…,尽管它并不完全相同。在那个例子中,当您使用 some_list * some_int 时会看到这种行为,这是解释器在运行时可以准确知道结果列表大小的另一个示例。但是,与您的列表文字情况相同,挖掘该源代码会很烦人。但是如果你 dis.dis 是一个列表文字表达式,你会看到 BUILD_LIST 操作码
  • 关心,因为我打算使用该值来计算压缩率。我需要知道差异来自哪里,这样我才能正确解释它。而且我认为这不是更重要的事情,我的意思是为什么我在乎。这本身就是一个有趣的问题。但是谢谢你的链接,奇怪的是我的任何搜索都没有弹出它。
  • 您应该非常警惕使用sys.getsizeof 处理这类事情。请注意,它 为您提供缓冲区的大小 + python 对象开销,缓冲区将是 PyObject* 数组的大小乘以系统上的 PyObject 指针(通常是机器字)。此外,通读这两个问题,您会发现底层缓冲区的增长方式可能会因各种因素而略有不同。你应该考虑改变你的方法。你在压缩什么?
  • 那么您应该使用str,或者更好的是bytes 对象。 bytes 是专门为这类事情而设计的,并且可以更可预测地扩展。我实际上不确定bytearray,这可能真的是最好的。 bytearray 可能会过度分配,但也许不会?在任何一种情况下,如果您从bytearray 创建bytes,它应该为您提供确切的大小+一些恒定的开销,比list 对象更可靠。听起来很有趣:)

标签: python list memory python-3.7


【解决方案1】:

开销是由于 Python 在 list 中分配了一些额外的“插槽”,以便您能够例如append 到列表中,无需分配新内存并将旧列表复制到那里。

您可以通过在列表中附加一些值来查看这一点:

>>> obj1.append('!')
>>> print(sys.getsizeof(obj1))
264

>>> obj2.append('!')
>>> print(sys.getsizeof(obj2))
272

如您所见,可以添加到 obj1 而不会增长,而 obj2 需要分配一个新的“块”。

这是一个实现细节,但是当您动态增加列表的大小时,这种行为通常会很好,因为它会减少程序的内存碎片(并提高速度)。

【讨论】:

  • 您似乎将 Python 列表视为展开的链表之类的东西。他们不是;他们是dynamic arrays。他们不分配块,并且没有额外的空间来避免内存碎片。 Python 列表使用 single contiguous buffer 的元素引用,并且有额外的空间以避免在每次追加时重新分配缓冲区。这允许摊销常数时间追加而不是线性时间追加。
  • @user2357112 无论是分配新列表并复制内容还是实际使用链表,在内存碎片和追加到列表的能力方面的行为都是相同的。但是,好吧,我可以更清楚地解释一下。
猜你喜欢
  • 2018-12-24
  • 1970-01-01
  • 2012-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多