两个原因:
CPython 中的每个字符串在其 C 对象标头中都有相当多的样板文件;在 Python 2 64 位系统上,空的 unicode 对象使用 52 个字节,这是 每个 unicode 对象的固定开销,甚至在计算它包含的数据之前。如果您有 114 万个 unicode 对象(不是像 u'' 这样的单例),那么仅每个对象的开销就使用了将近 6 GB。
1234563字符串;根据您的数字,您使用的是 4 字节/字符系统。因此,不是数据占用超过对象标头开销 543 MB,而是占用超过 2 GB。
标头问题在很大程度上是无法克服的(Python 对象总是会在标头上浪费几十个字节);每个 Python 对象都有很高的固定开销(如前所述,我的 x64 系统上的 sys.getsizeof(u'') 是 52,尽管只存储了 8 个字节的“真实”数据,str 的长度)。
但由于您的输入主要显示为 ASCII,您可以通过迁移到 Python 3 来减少内存使用;在现代 Py3 (3.3+ IIRC) 中,它们使用动态大小的存储空间来存储 str;仅使用 ASCII/latin-1 字符的 str 将使用每个字符一个字节(latin-1 使固定开销比 ASCII 略高,但每个字符的成本仍然为 1),而不是两个或四个(以及任何基本多语言平面将使用每个字符两个字节,而不是四个;只有非 BMP 字符串每个字符需要四个字节)。 str 的标头也小一些(sys.getsizeof('') == 49,而不是 52),因此您希望将标头的内存消耗减少约 350 MB,并为更紧凑的数据存储减少 1.5 GB(因为它主要是ASCII)。
只需使用 Py 3 并将代码更改为:
with open('tokens.txt', 'r', encoding='utf-8') as f:
tokens = f.read().split()
import sys
print(sys.getsizeof(tokens))
print(sum(sys.getsizeof(t) for t in tokens))
并且您应该看到字符串的内存使用减少了,在较长字符串的情况下显着减少(例如,在我的 Linux x64 安装上,u'examplestring' 在 Py2 上是 104 字节,用 4 字节/字符 unicode 编译,只有 62 Py3 上的字节)。
或者,作为一种廉价的hack,当你知道它是纯ASCII时,你可以尝试在Py2上从unicode转换回str;在 Py2 上,这两种类型在很大程度上是可互操作的,str 的每个对象开销更小(37 字节对 52 字节),并且只使用一个字节/字符。手动将 unicode 转换回 ASCII 是可行的,但它会减慢您的速度。为此,请将您的代码更改为:
# Open in binary mode
with open('tokens.txt', 'rb') as f:
# Defer decode and only do it for str with non-ASCII bytes
# producing list of mostly ASCII str with a few unicode objects
# when non-ASCII appears
tokens = [w.decode('utf-8') if max(w) > '\x7f' else w
for w in f.read().split()]
import sys
print sys.getsizeof(tokens)
print sum(sys.getsizeof(t) for t in tokens)
这应该为您节省约 1.7 GB 的每个对象标头,以及相同的约 1.5 GB 数据存储,以换取您可能会发现 Py2 所具有的 str/unicode 互操作性怪癖(并且是在 Py 3 中分离 bytes 和 str 的很大一部分动机。