一尘不染

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

python

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

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

Python在这里认识到新创建的“ a” +“ b”与内存中已经存在的“ ab”相同-不错。

现在考虑一个由N个长的州名组成的列表[“ Arizona”,“ Alaska”,“ Alaska”,“ California” …](在我的情况下为N〜500000)。
我看到50个不同的id()s⇒每个字符串“ Arizona” …仅存储一次,很好。
但是将列表写入磁盘,然后再次读回:“相同”列表现在具有N个不同的id(),增加了内存,请参见下文。

怎么了-有人能解释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内存(或任何程序的)中有两种字符串:

  • 唯一字符串的Ucache中的Ustring:节省内存,并且如果两个都在Ucache中,则可以使a == b快速
  • Ostrings,其他的,可以存储多次。

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

历史脚注: SPITBOL 统一所有字符串ca。1970年。


阅读 529

收藏
2020-02-22

共1个答案

一尘不染

在分配不可变对象(例如字符串)时,Python语言的每种实现都可以自由地做出自己的取舍-制作一个新对象,或者找到一个相等的对象,并使用一个以上的引用,从该语言的角度来看都很好观点看法。当然,在实践中,现实世界中的实现会做出合理的折衷:在定位这样的对象时,再引用一个合适的现有对象既便宜又容易,如果要找到合适的现有对象(可能会可能不存在),看起来可能需要很长时间才能搜索到。

因此,例如,在一个函数中多次出现相同字符串文字(在我所知道的所有实现中)将使用“对同一对象的新引用”策略,因为在构建该函数的常量池时,它非常容易快捷避免重复;但是跨单独的功能执行此操作可能是一项非常耗时的任务,因此,现实世界中的实现要么根本不执行此操作,要么仅在某些启发式确定的情况子集中执行此操作,而这些子集可以希望我们合理地权衡编译时间(通过搜索相同的现有常量而降低)与内存消耗(如果不断制作新的常量副本会增加内存消耗)。

我不知道Python的任何实现(或与此有关的其他具有常量字符串的语言,例如Java)在从文件中读取数据时会麻烦识别可能的重复项(以通过多个引用重用单个对象)的麻烦- -这似乎不是一个有希望的折衷办法(这里您要付出运行时的费用,而不是编译时的费用,所以这种折衷的吸引力就更小了)。当然,如果你知道(得益于应用层面的考虑),这样的不可变对象是大,非常容易出现很多重复,你可以很容易地实现自己的“常量池”战略(实习生可以帮助您为字符串做,但不难为自己而生,例如具有不可变项的元组,巨大的长整数,

2020-02-22