Python何时为相同的字符串分配新内存?

时间:2010-01-23 17:08:14

标签: python memory memory-management

两个具有相同字符的Python字符串,a == b, 可以共享内存,id(a)== id(b), 或者可能在内存中两次,id(a)!= id(b)。 尝试

ab = "ab"
print id( ab ), id( "a"+"b" )

这里Python认识到新创建的“a”+“b”是相同的 因为“ab”已经在记忆中 - 不错。

现在考虑一个N长的州名列表     [“亚利桑那州”,“阿拉斯加”,“阿拉斯加”,“加利福尼亚”......] (在我的情况下,N~500000) 我看到50个不同的id()s⇒每个字符串“Arizona”......只存储一次,很好 但是将列表写入磁盘并再次读回: “相同”列表现在有N个不同的id()s,更多内存,见下文。

为什么有人能解释Python字符串内存分配?

""" when does Python allocate new memory for identical strings ?
    ab = "ab"
    print id( ab ), id( "a"+"b" )  # same !
    list of N names from 50 states: 50 ids, mem ~ 4N + 50S, each string once
    but list > file > mem again: N ids, mem ~ N * (4 + S)
"""

from __future__ import division
from collections import defaultdict
from copy import copy
import cPickle
import random
import sys

states = dict(
AL = "Alabama",
AK = "Alaska",
AZ = "Arizona",
AR = "Arkansas",
CA = "California",
CO = "Colorado",
CT = "Connecticut",
DE = "Delaware",
FL = "Florida",
GA = "Georgia",
)

def nid(alist):
    """ nr distinct ids """
    return "%d ids  %d pickle len" % (
        len( set( map( id, alist ))),
        len( cPickle.dumps( alist, 0 )))  # rough est ?
# cf http://stackoverflow.com/questions/2117255/python-deep-getsizeof-list-with-contents

N = 10000
exec( "\n".join( sys.argv[1:] ))  # var=val ...
random.seed(1)

    # big list of random names of states --
names = []
for j in xrange(N):
    name = copy( random.choice( states.values() ))
    names.append(name)
print "%d strings in mem:  %s" % (N, nid(names) )  # 10 ids, even with copy()

    # list to a file, back again -- each string is allocated anew
joinsplit = "\n".join(names).split()  # same as > file > mem again
assert joinsplit == names
print "%d strings from a file:  %s" % (N, nid(joinsplit) )

# 10000 strings in mem:  10 ids  42149 pickle len  
# 10000 strings from a file:  10000 ids  188080 pickle len
# Python 2.6.4 mac ppc

添加25jan:
Python内存(或任何程序)中有两种字符串:

  • Ustrings,在一个独特字符串的Ucache中:这些节省内存,如果两者都在Ucache中,则快速== b;
  • Ostrings,其他人,可以存储任意次。

intern(astring)在Ucache(Alex +1)中加入了字符串; 除此之外,我们对Python如何将Ostrings转移到Ucache一无所知 - 在“ab”之后,“a”+“b”是如何进入的? (“文件中的字符串”毫无意义 - 无法知道。)
简而言之,Ucaches(可能有几个)仍然模糊不清。

历史脚注: SPITBOL unquified所有字符串ca. 1970。

5 个答案:

答案 0 :(得分:39)

Python语言的每个实现都可以自由地在分配不可变对象(例如字符串)时做出权衡 - 要么创建新对象,要么找到现有的等价并再使用一个参考它,从语言的角度来看就好了。当然,在实践中,现实世界的实现需要合理的妥协:在定位这样的对象时,再一次提及合适的现有对象既便宜又容易,只要找到一个合适的现有对象(可能是可能不存在)看起来可能需要很长时间才能进行搜索。

因此,例如,在单个函数中多次出现相同的字符串文字(在我所知的所有实现中)都使用“对同一对象的新引用”策略,因为在构建该函数的常量池时,它非常快并且容易避免重复;但是在单独的函数中执行此操作可能是一项非常耗时的任务,因此现实世界的实现要么根本不执行,要么只在一些启发式标识的子集中执行希望能够合理地权衡编译时间(通过搜索相同的现有常量来减慢速度)与内存消耗(如果继续保留新的常量副本,则会增加)。

我不知道Python的任何实现(或者其他具有常量字符串的语言,例如Java),在从以下方法读取数据时,需要识别可能的重复项(通过多个引用重用单个对象)一个文件 - 它似乎不是一个有希望的权衡(在这里你需要支付运行时,而不是编译时间,因此权衡更具吸引力)。当然,如果您知道(由于应用程序级别的考虑因素)这些不可变对象很大并且很容易出现重复,那么您可以非常轻松地实现自己的“常量池”策略(intern可以帮助您实现它对于字符串,但不难推出自己的字符串,例如,带有不可变项,巨大长整数等的元组。)

答案 1 :(得分:16)

我强烈怀疑Python在这里表现得像许多其他语言一样 - 在源代码中识别字符串常量并使用公用表来表示那些,但应用它们动态创建字符串时的规则。这是有道理的,因为源代码中只有一组有限的字符串(当然,Python允许你动态地评估代码),而你在程序过程中创建大量字符串的可能性更大。

这个过程通常被称为 interning - 实际上也是this page的外观,它也被称为Python实习。

答案 2 :(得分:13)

附注:了解Python中对象的生命周期非常重要。请注意以下会话:

Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10) 
[GCC 4.3.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a="a"
>>> b="b"
>>> print id(a+b), id(b+a)
134898720 134898720
>>> print (a+b) is (b+a)
False

您认为通过打印两个单独的表达式的ID并注意“它们相等,两个表达式必须相等/等同/相同”是错误。单行输出并不一定意味着它的所有内容都是在同一时刻创建和/或共存的。

如果您想知道两个对象是否是同一个对象,请直接询问Python(使用is运算符)。

答案 3 :(得分:3)

x = 42
y = 42
x == y #True
x is y #True
  

在这种互动中,X和Y应该是   ==(相同的值),但不是(相同的对象),因为我们运行了两个不同的   文字表达。因为小   整数和字符串被缓存和   但重用,告诉我们他们   引用相同的单个对象。

     

事实上,如果你真的想看   引擎盖下,你总能问   Python有多少引用   使用 getrefcount 对象   标准sys模块中的功能   返回对象的引用计数。   这种行为反映了众多行为中的一个   Python优化其模型的方法   执行速度。

Learning Python

答案 4 :(得分:1)

我找到了一篇很好的文章来解释CPython的intern行为: http://guilload.com/python-string-interning/

简而言之:

  1. CPython中的字符串对象具有一个标志,用于指示它是否位于intern中。
  2. 通过将字符串存储在带有键的普通字典中来实现字符串的插入,并且值是字符串的指针。这仅接受string类。
  3. 实习生可以帮助Python减少内存消耗,因为对象可以引用相同的内存地址,并可以提高比较速度,因为它只需要比较字符串的指针即可。
  4. Python在编译过程中执行intern,这意味着仅文字字符串(或可以在编译时计算字符串,例如'hello'+'world')
  5. 您的问题:仅插入长度为0或长度1或仅包含ASCII字母(a-z,A-Z,0-9)的字符串
  6. Intern在Python中工作,因为字符串是不可变的,否则就没有意义。

这是一篇非常好的文章,我强烈建议您访问他的网站并检查其他网站,这值得我们花时间。