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

2025-02-08 08:52:00
admin
原创
50
摘要:问题描述:两个具有相同字符的 Python 字符串 a == b 可能共享内存,id(a) == id(b),或者可能在内存中两次,id(a) != id(b)。尝试ab = "ab" print id( ab ), id( "a"+"b" ) 这里...

问题描述:

两个具有相同字符的 Python 字符串 a == b 可能共享内存,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() ⇒ 每个字符串“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( "
".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 = "
".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

添加于 1 月 25 日:

Python 内存中(或任何程序的内存中)有两种字符串:

  • Ustrings,在唯一字符串的 Ucache 中:这些节省内存,并且如果两者都在 Ucache 中,则使 a == b 变得快速

  • Ostrings,其他的,可以存储任意次数。

intern(astring)将字符串放入 Ucache(Alex +1);除此之外,我们完全不知道 Python 如何将 Ostrings 移动到 Ucache 中 —— “a”+“b” 是如何在 “ab” 之后进入的?(“来自文件的字符串”毫无意义 —— 没有办法知道。)

简而言之,Ucaches(可能有多个)仍然很模糊。

历史注脚:
SPITBOL
于 1970 年左右将所有字符串唯一化。


解决方案 1:

Python 语言的每种实现都可以自由地在分配不可变对象(例如字符串)时做出自己的权衡——从语言的角度来看,要么创建一个新的对象,要么找到一个现有的相等对象并使用对它的一个引用,这都是可以的。当然,在实践中,现实世界的实现会达成合理的妥协:当找到这样的对象既便宜又容易时,对合适的现有对象再多一个引用,如果找到合适的现有对象(可能存在也可能不存在)的任务看起来可能需要很长时间搜索,那么只需创建一个新对象即可。

因此,例如,在单个函数中多次出现相同的字符串文字(在我所知道的所有实现中)将使用“对同一对象的新引用”策略,因为在构建该函数的常量池时,避免重复非常快速且容易;但是在不同的函数中执行此操作可能是一个非常耗时的任务,因此现实世界的实现要么根本不这样做,要么只在某些启发式识别的子集中执行此操作,在这些子集中,人们可以希望在编译时间(通过搜索相同的现有常量而减慢速度)与内存消耗(如果不断生成常量的新副本则会增加)之间取得合理的权衡。

我不知道 Python 的任何实现(或者其他具有常量字符串的语言,例如 Java)会在从文件读取数据时费力地识别可能的重复项(通过多个引用重用单个对象)——这似乎不是一个有希望的权衡(在这里你要付出的是运行时,而不是编译时间,所以这种权衡就更没有吸引力了)。当然,如果您知道(由于应用程序级别的考虑)这样的不可变对象很大并且很容易出现许多重复,您可以很容易地实现自己的“常量池”策略(intern可以帮助您为字符串做到这一点,但对于例如具有不可变项的元组、巨大的长整数等,自己实现并不难)。

解决方案 2:

我强烈怀疑 Python 的行为与许多其他语言类似 - 识别源代码中的字符串常量并使用通用表,但在动态创建字符串时应用相同的规则。这是有道理的,因为源代码中只有一组有限的字符串(当然,Python 允许您动态评估代码),而更有可能的是,您将在程序运行过程中创建大量字符串。

该过程通常称为实习—— 事实上,从本页来看,它在 Python 中也被称为实习。

解决方案 3:

附注:了解 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运算符)。

解决方案 4:

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

在此交互中,X 和 Y 应该是 ==(相同值),但不是 is(相同对象),因为我们运行了两个不同的文字表达式。但是,由于小整数和字符串被缓存和重用,因此 is 告诉我们它们引用的是同一个对象。

事实上,如果您真的想深入了解,您可以随时使用标准 sys 模块中的getrefcount
函数询问 Python 对象有多少个引用,该函数返回对象的引用计数。此行为反映了 Python 优化其模型以提高执行速度的众多方式之一。

学习 Python

解决方案 5:

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

简而言之:

  1. CPython 中的字符串对象有一个标志来指示它是否在intern.

  2. 通过将字符串存储在普通字典中来驻留字符串,其中键和值是字符串的指针。这string仅接受类。

  3. 由于对象可以引用相同的内存地址,因此 Interning 可以帮助 Python 减少内存消耗,并且由于只需要比较字符串的指针,因此可以加快比较速度。

  4. Pythonintern在编译过程中执行此操作,这意味着只有文字字符串(或可以在编译时计算的字符串,如“hello”+“world”)

  5. 对于您的问题:只有长度为 0 或长度为 1 或仅包含 ASCII 字母(az、AZ、0-9)的字符串才会被保留

  6. Intern由于字符串是不可变的,因此在 Python 中可以工作,否则就没有意义。

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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用