代码之家  ›  专栏  ›  技术社区  ›  ooboo

Python内存模型

  •  11
  • ooboo  · 技术社区  · 17 年前

    n = (2**32)**2
    for i in xrange(10**7)
      li[i] = n
    

    for i in xrange(10**7)
      li[i] = i**2
    

    4 回复  |  直到 17 年前
        1
  •  18
  •   Nathaniel Ford iFail    7 年前

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

    编辑:请注意,引用始终指向 物体

    >>> 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
    

    我们从第一张打印中看到,列表中的两个条目 a n 仍然参考前一个。

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

    例如,一个1000个项目的容器,所有项目都是不同的小整数(每个项目的值可以容纳2个字节),将大约2000个字节的数据作为 array.array('h') ,但大约20000作为一个 list

    arr

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

    不过,这很奇怪——毕竟,这是怎么回事 对存储的整数的引用? id(变量)给出一个整数

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

    id(v) 给出(仅在CPython上)指针的数值(作为唯一标识对象的方便方法)。列表可能是异构的(一些项可能是整数,另一些是不同类型的对象),因此将一些项存储为指向PyObject的指针和其他项的指针是不明智的选择(每个对象还需要一个类型指示,在CPython中,至少需要一个引用计数)-- array.array

    语言规范完全允许Python实现尝试更微妙的优化技巧,只要它保持语义不变,但据我所知,目前还没有一种方法可以解决这个特定问题(你可以尝试破解PyPy后端,但如果检查int和非int的开销超过了希望的收益,也不要感到惊讶)。

    此外,如果我 分配 2**64 改为每个插槽 当n包含a时,分配n 参考 2**64 我只写1?

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

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

    在实践中,CPython(我相信其他实现,但我不太熟悉它们的内部)使用了一个足够

    一种语言 它有几个实现,在很多这样的细节上可能(合法和正确地)有所不同——每个人,包括像我这样的书呆子,在谈论流行的C编码实现时,往往只说“Python”而不是“CPython”(除非在像这样的上下文中,语言和实现之间的区别至关重要;-)。然而,区别 非常重要,值得偶尔重复一次。

        2
  •  6
  •   zdan    17 年前

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

        3
  •  3
  •   Lennart Regebro    17 年前

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

    array[i] = n 您创建了对以下值的新引用 n 请注意,不是变量,而是值。然而,在第二种情况下,当你这样做时 array[i] = i**2 您创建一个新值,并引用这个新值。这当然会消耗更多的内存。

    事实上,Python将继续重用相同的值,即使重新计算,也只使用对它的引用。例如:

    l = []
    x = 2
    for i in xrange(1000000):
        l.append(x*2)
    

    通常不会使用超过

    l = []
    x = 2
    for i in xrange(1000000):
        l.append(x)
    

    然而,在以下情况下

    l = []
    x = 2
    for i in xrange(1000000):
        l.append(i)
    

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

        4
  •  0
  •   mtasic85 Ed Heal    17 年前

    arr[i] n 或由此产生的物体 i * 2 .

    在第一示例中, ,如果需要,GC必须为这个新的结果对象分配空间,然后使用它的引用。