【问题标题】:python - why do immutable objects not occupy same memorypython - 为什么不可变对象不占用相同的内存
【发布时间】:2023-03-06 04:57:01
【问题描述】:

我正在使用 micropython,但没关系

>>> b = [None]*40
>>> gc.collect(); gc.mem_free(); dir(); sys.modules
101
7008
['sys', '__name__', 'a', 'gc', 'b']
{}
>>> for i in range(40):
...     b[i] = (255, 0, 0)
...     gc.collect(); gc.mem_free();
...
...
...
5
6800
0
6768
0
6736
0
6704
0
6672
0
6640
0
6608
0
6576
0
6544
0
6512
0
6480
0
6448
0
6416
0
6384
0
6352
0
6320
0
6288
0
6256
0
6224
0
6192
0
6160
0
6128
0
6096
0
6064
0
6032
0
6000
0
5968
0
5936
0
5904
0
5872
0
5840
0
5808
0
5776
0
5744
0
5712
0
5680
0
5648
0
5616
0
5584
0
5552
>>>

小数字是收集的对象gc.collect() 的数量,大数字是存在多少可用内存。

(255, 0, 0) 是一个不可变的元组,它包含不可变的对象,为什么每次赋值后空闲内存量会减少?

如果一个对象是不可变的,那么 Python 复制它的意义何在?
为什么不给每个b[i]分配相同的“指针”?

更新

我在元组(55555555555555555555, 55555555555555555555)中使用了更大的数字,内存使用量是一样的。

    >>> gc.collect(); gc.mem_free(); dir(); sys.modules
5
6368
['sys', '__name__', 'gc', 'i']
{}
>>> b = [None]*40
>>> for i in range(40):
...     b[i] = (55555555555555555555,55555555555555555555)
...     id(b[i])
...     gc.collect(); gc.mem_free()
...
...
...
5347968
10
5824
5347136
0
5808
5347312
0
5792
5347456
0
5776
5347536
0
5760
5347552
0
5744
5347696
0
5728
5347712
0
5712
5347984
0
5696
5348176
0
5680
5348192
0
5664
5348208
0
5648
5348224
0
5632
5348240
0
5616
5348256
0
5600
5348272
0
5584
5348288
0
5568
5348608
0
5552
5348640
0
5536
5348656
0
5520
5348672
0
5504
5348688
0
5488
5348704
0
5472
5348720
0
5456
5348736
0
5440
5348848
0
5424
5348864
0
5408
5348880
0
5392
5348896
0
5376
5348912
0
5360
5348928
0
5344
5348944
0
5328
5349104
0
5312
5349120
0
5296
5349136
0
5280
5349152
0
5264
5349168
0
5248
5349184
0
5232
5349200
0
5216
5349216
0
5200
>>>

但是当我使用整数 (55555555555555555555) 时,内存使用量不会随着我的迭代而改变。

【问题讨论】:

  • 你如何确定它的使用指针? (255,0,0) 在大小上似乎与指向自身的指针没有太大区别。
  • @Octopus 因为我已经预先分配了大小为 40 的列表,该列表将指向 None 对象又名 const mp_obj_none_t mp_const_none_obj = {{&mp_type_NoneType}};
  • 跟踪哪些可以重用的对象可以重用需要时间和内存。与创建新副本相比,开销通常不值得。
  • 为了让不可变对象共享内存,Python 必须识别将要创建的对象与现有对象相同。在最坏的情况下,这将需要检查 每个现有的相同类型的对象 以查看是否存在重复项。 Python 确实在可能且容易检测到重复项的特定情况下(特别是小整数)执行此操作,但我相信在元组的情况下唯一的重复数据删除是只有一个空元组。 (不知道 Micropython 是否以同样的方式做事。)
  • @Adrian: (55555555555555555555) 不是tuple。这是一个简单的int,周围有多余的分组括号。如果你想要一个单元素 tuple,你需要添加一个尾随逗号:(55555555555555555555,)

标签: python immutability


【解决方案1】:

因为在解释器中对所有不可变对象的通用实习很复杂,并且添加了大量代码,很少能节省任何有价值的东西。

也就是说,您的代码确实在 CPython 参考解释器上使用了 tuple 的单个副本。这是一个实现细节,所以每个解释器都可以在这里做出自己的决定,我猜 Micropython 没有选择这样做(可能是为了让解释器足够简单,以便在较弱的硬件上运行)。

看起来 Micropython 为 int 常量执行缓存,但不为 tuples 执行缓存; tuples 更难处理(至少最初,CPython 在主 AST 阶段没有这样做,它只是在生成的字节码上运行一个窥孔优化器来转换 LOAD_CONST 的运行,然后是 BUILD_TUPLE 使用只有LOAD_CONST 产生LOAD_CONST 的结果tuple),所涉及的额外工作可能被认为不值得。

【讨论】:

  • 但是当我使用 int (55555555555555555555) 时,内存使用量并没有增加。是否仅适用于某些不可变对象?
  • @Adrian:Micropython 可能会在缓存 int 常量上做出一些努力,但不会在 tuple 常量上做出一些努力。 CPython 有一个小的int 缓存,它在任何地方都可以使用,但只涵盖从(实现细节)-5256(包括)的ints。它确实在函数的常量数组中缓存了较大的值,但这仅适用于常量(其中小的int 缓存涵盖了int 的所有用途,甚至是动态计算的值),并且它不是全局缓存。您处于“实施细节”领域;没有一个答案。
  • CPython 将重用相同的元组,即使在问题中演示的交互式案例中也是如此。问题中的行为特定于 MicroPython(我没有安装,也不是专家,所以我不能说更多)。
  • @user2357112:是的,我只是在测试它。 CPython,它实际上甚至在全局范围内缓存常量;对于全局和函数范围,它看起来是一个每次使用的缓存(所以print(id((255, 0, 0), id((255, 0, 0)) 显示了两个独立的副本)。用dis 检查__main__ 模块有点困难,所以我认为解决这个问题需要直接检查C 源代码,当OP 想要Micropython 的细节时,这似乎不值得。我更新了答案以删除未经证实的声明。
猜你喜欢
  • 2021-05-16
  • 2019-01-02
  • 2019-04-24
  • 1970-01-01
  • 2019-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-21
相关资源
最近更新 更多