首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python内存模型

Python内存模型
EN

Stack Overflow用户
提问于 2009-06-29 18:12:55
回答 4查看 4.1K关注 0票数 11

假设我这样做了,我有一个非常大的列表(是的,我知道代码非常不容易理解,但是为了例子的缘故..):

代码语言:javascript
复制
n = (2**32)**2
for i in xrange(10**7)
  li[i] = n

工作正常。但是:

代码语言:javascript
复制
for i in xrange(10**7)
  li[i] = i**2

消耗的内存要大得多。我不明白为什么会这样-存储大数字需要更多的位,而在Java中,第二种选择确实更节省内存……

有人对此有什么解释吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2009-06-29 18:23:07

Java特殊情况-使用一些值类型(包括整数),以便它们按值存储(而不是像其他所有类型一样,按对象引用)。Python不会对这种类型进行特殊处理,因此将n赋给列表(或其他普通Python容器)中的许多条目不需要进行复制。

编辑:请注意,引用总是指向对象,而不是“指向变量”--在Python (或Java)中没有“对变量的引用”这样的东西。例如:

代码语言:javascript
复制
>>> 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

从第一次打印中我们可以看到,list a中的两个条目引用的对象与n引用的完全相同--但是当重新分配n时,it现在引用了一个不同的对象,而a中的两个条目仍然引用前一个。

array.array (来自Python标准库模块array)与列表非常不同:它保持同构类型的紧凑副本,每项占用尽可能少的位来存储该类型值的副本。所有普通容器都保留引用(在C编码的Python运行时内部实现为指向PyObject结构的指针:在32位构建中,每个指针占用4个字节,每个PyObject至少16个字节,包括指向类型的指针、引用计数、实际值和malloc四舍五入),而数组则不能(因此它们不能是异构的,不能包含一些基本类型以外的项,等等)。

例如,一个包含1000个项目的容器,其中所有项目都是不同的小整数(每个项目的值可以包含在2个字节内),作为array.array('h')将占用大约2,000字节的数据,而作为list将占用大约20,000字节的数据。但是如果所有的项都是相同的数字,数组仍然需要2000字节的数据,列表只需要20字节左右[在每一种情况下,你都必须为容器对象本身增加大约16或32字节,除了用于数据的内存]。

然而,尽管问题说的是“数组”(甚至在标记中),但我怀疑它的arr实际上是一个数组--如果是,它就不能存储(2** 32 )*2 (数组中最大的int值是32位),并且问题中报告的内存行为实际上不会被观察到。因此,问题实际上可能是关于列表,而不是数组。

ooboo编辑:@ooboo的评论提出了很多合理的后续问题,而不是试图在评论中压缩详细的解释,我将它移到了这里。

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

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

id(v)给出(仅在CPython上)指针的数值(作为唯一标识对象的方便方法)。列表可以是异构的(一些项可能是整数,另一些是不同类型的对象),因此将一些项作为指向PyObject的指针存储,而将其他项作为指针以不同方式存储(每个对象还需要一个类型指示,至少在CPython中还需要一个引用计数) -- array.array是同构的,而且受到限制,因此它确实可以(并且确实)存储项的值的副本,而不是引用(这通常比较便宜,但对于相同项大量出现的集合,例如绝大多数项为0的稀疏数组)。

Python实现将被语言规范完全允许尝试更微妙的优化技巧,只要它保持语义不变,但据我所知,目前还没有针对这个特定问题的实现(您可以尝试破解PyPy后端,但如果检查整型与非整型的开销超过了期望的收益,请不要惊讶)。

另外,当n包含对2**64的引用时,如果我将2**64赋值给每个插槽而不是赋值n,会有区别吗?当我只写1的时候会发生什么?

这些都是完全允许每个实现做出的实现选择的示例,因为保留语义并不难(所以即使假设3.1和3.2在这方面的表现也可能不同)。

当您使用int文本(或不可变类型的任何其他文本)或其他表达式生成此类类型的结果时,由实现决定是无条件地创建该类型的新对象,还是花费一些时间在这些对象中检查是否有可以重用的现有对象。

在实践中,CPython (我相信其他实现,但我不太熟悉它们的内部结构)使用足够小的整数的单个副本(以PyObject形式保留了一个预定义的C数组,其中包含几个小整数值,可以在需要时使用或重用),但通常不会特意寻找其他现有的可重用对象。

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

有时很难记住Python是一种语言,而且它有几个实现可能(合法且正确地)在许多这样的细节上存在差异--每个人,包括像我这样的书呆子,在谈论流行的C代码实现时倾向于只说"Python“而不是"CPython”(除了在这种上下文中,区分语言和实现是至关重要的;-)。然而,区别是相当重要的,并且很值得偶尔重复一遍。

票数 18
EN

Stack Overflow用户

发布于 2009-06-29 18:21:42

在第一个示例中,您存储的是相同的整数len(arr)时间。所以python只需要在内存中存储整数一次,并引用它len(arr)次。

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

票数 6
EN

Stack Overflow用户

发布于 2009-06-29 18:17:38

您只有一个变量n,但是您创建了许多i**2。

发生的事情是Python使用引用。每次执行array[i] = n操作时,都会创建一个对n值的新引用。请注意,不是变量,而是值。然而,在第二种情况下,当您执行array[i] = i**2时,您创建了一个新值,并引用了这个新值。这当然会占用更多的内存。

事实上,Python将继续重用相同的值,并仅使用对它的引用,即使重新计算也是如此。举个例子:

代码语言:javascript
复制
l = []
x = 2
for i in xrange(1000000):
    l.append(x*2)

通常使用的内存不会超过

代码语言:javascript
复制
l = []
x = 2
for i in xrange(1000000):
    l.append(x)

但是,在这种情况下

代码语言:javascript
复制
l = []
x = 2
for i in xrange(1000000):
    l.append(i)

I的每个值都将获得一个引用,因此会保存在内存中,与其他示例相比,会占用大量内存。

(Alex指出了术语中的一些混淆。在python中有一个名为array的模块。这些类型的数组存储整数值,而不是像Pythons normal list对象那样对对象的引用,但在其他方面行为相同。但由于第一个示例使用的值不能存储在这样的数组中,因此这里不太可能出现这种情况。

相反,问题很可能是使用单词array,因为它在许多其他语言中使用,这与Pythons列表类型相同。)

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1059674

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档