【问题标题】:Why are Unicode strings having a different memory footprint in Python 2 and 3? [duplicate]为什么 Unicode 字符串在 Python 2 和 3 中具有不同的内存占用? [复制]
【发布时间】:2019-01-14 21:08:17
【问题描述】:

在 Python 2 中,一个空字符串正好占用 37 个字节,

>>>> print sys.getsizeof('')
37

在 Python 3.6 中,相同的调用会输出 49 个字节,

>>>> print(sys.getsizeof(''))
49

现在我认为这是因为 在 Python 3 中,所有字符串现在都是 Unicode。但是,令我惊讶的是,这里有一些令人困惑的输出,

Python 2.7

>>>> print sys.getsizeof(u'')
52
>>>> print sys.getsizeof(u'1')
56

Python 3.6

>>>>print(sys.getsizeof(''))
49
>>>>print(sys.getsizeof('1'))
50
  1. 空字符串的大小不同。
  2. 在 Python 2 中添加一个字符时需要 4 个额外的字节,而在 Python 3 中只需要一个字节

为什么两个版本的内存占用不同?

编辑

我指定了我的 python 环境的确切版本,因为不同的 Python 3 版本之间存在差异。

【问题讨论】:

标签: python python-3.x python-2.7 memory unicode


【解决方案1】:

当然是有原因的,但实际上,这对于任何实际目的都无关紧要。如果您有一个 Python 系统,其中您必须在内存中保留如此多的字符串以接近系统内存,您应该通过(1)尝试在内存中延迟加载/创建字符串或(2)使用一个字节来优化它面向高效的二进制结构来处理你的数据,比如 Numpy 提供的,或者 Python 自己的bytearray。

空字符串文字(来自 Py2 的 unicode 文字)的更改可能与您正在查看的版本之间的任何实现细节有关,即使是编写 C 代码以直接与 Python 字符串交互,这也无关紧要:即使那些应该仅通过 API 触摸字符串。

现在,为什么 Python 3 中的字符串只是将其大小增加“1”字节,而在 Python 2 中它增加了 4 个字节的具体原因是PEP 393。

在 Python 3.3 之前,Python 中的任何(unicode)字符串都会为每个字符使用固定的 2 个字节或固定的 4 个字节的内存 - 并且使用本机代码的 Python 解释器和 Python 模块必须被编译为仅使用其中一个这些种类。 IE。即使版本匹配,您实际上也可能拥有不兼容的 Python 二进制文件,因为在构建时选择了字符串宽度选项 - 构建被称为“窄构建”和“宽构建”。使用上述 PEP 391,Python 字符串在实例化时确定其字符大小,具体取决于它包含的最宽 unicode 代码点的大小。包含前 256 个代码点(相当于 Latin-1 字符集)中包含的点的字符串每个字符仅使用 1 个字节。

【讨论】:

  • 我同意你的观点,坦率地说,当我发现这一点时,我正在玩弦乐。我的问题没有特别的用例,纯粹是出于好奇。但是,我想认为,如果他们真的努力实现使用 1 字节而不是固定的 2 或 4 字节的编码系统,应该是有原因的。我的意思是这意味着它有点相关,我错了吗?
  • @scharette 这是一种优化,可以使许多程序受益,而程序员甚至不必知道它。就像您不必了解 PyPy 的 JIT 如何让您的代码运行得更快一样,您也不必了解 CPython 3.7 的 Unicode 存储如何让您的代码占用更少的内存。它记录了您确实需要知道但大多数人从不知道的罕见情况。 (事实上​​,我怀疑大多数人是出于好奇,或者是出于对 CPython 本身的黑客攻击,而不是因为他们需要为 Python 程序学习它。)
  • 确实,即使在使用 C 代码时,您也应该只通过 API 触摸字符串。但是,不幸的是,3.3 必须更改 C API,弃用大多数旧函数,以使优化工作高效且顺利,这些更改将您引向 PEP 393,因此它并不像理想情况下那样不可见。
  • @abarnert 没错,我只是认为这些问题应该在 SO 中占有一席之地,因为它们有时可以让我们更好地理解一个复杂的概念。
  • @scharette 当然,这就是我写this horrible module 之类的东西并回答有关它的问题的原因。我当然希望除了更好地理解 CPython 之外,没有人真正将它用于其他任何事情。
【解决方案2】:

在内部,Python 3 现在以四种不同的编码存储字符串,并为每个字符串选择不同的编码。这些编码是 ASCII、LATIN-1、UCS-2 和 UTF-32。每个都能够表示 Unicode 字符的不同子集,并且具有以下有用属性:索引 i 处的元素也是索引 i 处的 Unicode 代码点。

In [1]: import sys

In [2]: sys.getsizeof('\xFF')
Out[2]: 74

In [3]: sys.getsizeof('X\xFF')
Out[3]: 75

In [4]: sys.getsizeof('\u0100')
Out[4]: 76

In [5]: sys.getsizeof('X\u0100')
Out[5]: 78

In [6]: sys.getsizeof('\U00010000')
Out[6]: 80

In [7]: sys.getsizeof('X\U00010000')
Out[7]: 84

您可以看到,向字符串添加额外的字符(在本例中为 'X')会导致该字符串占用额外的空间,具体取决于字符串其余部分中包含的值。

这个系统是在PEP-0393 中提出的,并在 Python 3.3 中实现。早期版本的 Python 使用较旧的 unicode 表示,它总是使用每个...元素 2 或 4 个字节(我不愿说“字符”),具体取决于编译时选项,它们不能混合使用。

【讨论】:

  • 值得一提的是,相比之下,Python 2(和早期的 Python 3)以 UTF-32 存储 unicode 字符串。 (或者,如果您使用“窄构建”,它会将它们存储在 UTF-16 中,并且就像代理是单独的字符一样。)
  • @abarnert:Python 3 的早期版本也这样做。
  • 是的,这就是我说“(和早期的 Python 3)”的原因。
  • 该死的我的眼睛!尽管如此,该信息已包含在答案中。
  • 顺便说一句,核心开发人员也犹豫说“字符”。每当他们添加 Unicode 2.0 支持(在 Python 2.1-ish 附近?)并引入窄构建时,他们从 C API 文档中删除了“字符”一词,并确保将它们明确称为“Py_UNICODE 值”之类的东西。 (但是,奇怪的是,直到 3.2,他们仍然引用 UCS2 而不是 UTF-16。)
猜你喜欢
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 2015-04-10
  • 2016-08-19
  • 2020-01-30
  • 1970-01-01
  • 2019-01-17
  • 1970-01-01
相关资源
最近更新 更多