Python内存模型

oob*_*boo 10 python memory arrays model

我有一个非常大的列表假设我这样做(是的,我知道代码非常单一,但为了示例的缘故......):

n = (2**32)**2
for i in xrange(10**7)
  li[i] = n
Run Code Online (Sandbox Code Playgroud)

工作良好.然而:

for i in xrange(10**7)
  li[i] = i**2
Run Code Online (Sandbox Code Playgroud)

消耗大量内存.我不明白为什么 - 存储大数字需要更多位,而在Java中,第二个选项确实更节省内存...

有没有人对此有解释?

Ale*_*lli 17

Java特殊情况下的一些值类型(包括整数),以便它们按值存储(而不是像对象一样通过对象引用).Python没有特殊情况这样的类型,因此将n分配给列表(或其他普通Python容器)中的许多条目不必进行复制.

编辑:请注意,引用始终是对象,而不是"变量" - 在Python(或Java)中没有"对变量的引用"这样的东西.例如:

>>> n = 23
>>> a = [n,n]
>>> print id(n), id(a[0]), id(a[1])
8402048 8402048 8402048
>>> n = 45
>>> print id(n), id(a[0]), id(a[1])
8401784 8402048 8402048
Run Code Online (Sandbox Code Playgroud)

我们从第一个打印中看到列表中的两个条目都a指向与引用完全相同的对象n- 但是当n重新分配时,现在引用不同的对象,而两个条目a仍然引用前一个.

一个array.array(Python标准库模块阵列)是从列表中非常不同:它保持一个均匀的类型的紧凑的副本,因为需要用于存储该类型的值的拷贝以每件尽可能少的比特.所有普通容器都保留引用(在C编码的Python运行时内部实现为PyObject结构的指针:每个指针,在32位构建上,需要4个字节,每个PyObject至少16个左右[包括指向类型的指针,引用计数,实际值和malloc四舍五入]),数组没有(因此它们不能是异构的,除了几个基本类型之外不能有项目等).

例如,一个1000项容器,所有项目都是不同的小整数(其值可以分别为2个字节),将需要大约2,000字节的数据作为array.array('h'),但大约20,000作为a list.但是如果所有项目都是相同的数字,那么数组仍然会占用2000个字节的数据,这个列表只需要20个左右[[在这些情况下,你必须为容器对象添加另外16个或32个字节)正确的,除了数据的内存]].

然而,虽然问题是"数组"(即使在标签中),但我怀疑它arr实际上是一个数组 - 如果是,它无法存储(2**32)*2(数组中最大的int值为32比特)并且实际上不会观察到问题中报告的记忆行为.所以,问题可能实际上是关于列表,而不是数组.

编辑:@ooboo的评论提出了许多合理的后续问题,而不是试图在评论中强调详细解释我在这里移动它.

但这很奇怪 - 毕竟,对整数的引用是如何存储的?id(变量)给出一个整数,引用本身就是一个整数,使用整数是不是更便宜?

CPython存储引用作为PyObject的指针(Jython和IronPython,用Java和C#编写,使用那些语言的隐式引用; PyPy,用Python编写,具有非常灵活的后端,可以使用许多不同的策略)

id(v)给出(仅在CPython上)指针的数值(就像一个唯一标识对象的方便方法).列表可以是异构的(某些项可能是整数,其他项可能是不同类型的对象)因此将一些项存储为PyObject和其他项的指针不是一个明智的选项(每个对象也需要类型指示,并且在CPython中,参考计数,至少) - array.array是同质的和有限的,所以它可以(并且确实)存储项目的价值而不是参考的副本(这通常更便宜,但不适用于同一项目看起来很多的集合,例如作为稀疏数组,绝大多数项目都是0).

语言规范完全允许Python实现尝试更优化的微妙技巧,只要它保持语义不受影响,但据我所知目前没有针对此特定问题(你可以尝试攻击PyPy后端,但不要如果检查int与非int的开销超过了所希望的收益,那就不会感到惊讶.

另外,如果我为每个插槽分配2 64而不是分配n,当n保持对2 64 的引用时,它会有所不同吗?当我写1时会发生什么?

这些是完全允许每个实现完成的实现选择的示例,因为保留语义并不困难(因此假设,即使3.1和3.2在这方面可能表现不同).

当你使用int literal(或任何其他不可变类型的文字)或产生这种类型的结果的其他表达式时,由实现来决定是否无条件地创建该类型的新对象,或者花一些时间检查这些对象以查看是否存在可以重用的现有对象.

在实践中,CPython(我相信其他实现,但我不太熟悉它们的内部)使用足够小的整数的单个副本(保持PyObject形式的几个小整数值的预定义C数组,准备使用或在需要时重用)但一般不会寻找其他现有的可重用对象.

但是,例如,同一函数中的相同文字常量很容易编译为函数常量表中的单个常量对象的引用,因此这是一个非常容易完成的优化,我相信每个当前的Python实现都会执行它.

它有时候很难记住,因为Python是一种语言,并且它有许多实现可能(合法且正确地)在很多这样的细节中有所不同 - 每个人,包括像我这样的小学生,倾向于只说"Python"而不是" CPython"在谈论流行的C编码实现时(在这样的上下文中除外,其中绘制语言和实现之间的区别是最重要的;-).但是,区别非常重要的,也是值得的,而重复一次.

  • 它存储对你构建一个n时引用的同一个对象的引用; 然后你切换名称n来引用另一个对象.它总是引用*对象*(这就是为什么我说"对象引用",你知道! - ),*从不*引用*到名字*,这似乎是你在混淆的东西(没有这样的东西引用Pythor或Java中的名称,仅引用对象). (5认同)

zda*_*dan 5

在第一个示例中,您将存储相同的整数len(arr)次.所以python只需要在内存中存储一​​次整数并将其引用为len(arr)次.

在第二个示例中,您将存储len(arr)不同的整数.现在python必须为len(arr)整数分配存储,并在每个len(arr)槽中引用它们.

  • 整数*是*对象. (4认同)