我有一个543 MB的txt文件,其中包含一行空格分隔的utf-8标记:
aaa algeria americansamoa appliedethics accessiblecomputing ada anarchism ...
但是,当我将这个文本数据加载到python列表中时,它使用~8 GB的内存(列表约900 MB,令牌约8 GB):
with open('tokens.txt', 'r') as f:
tokens = f.read().decode('utf-8').split()
import sys
print sys.getsizeof(tokens)
# 917450944 bytes for the list
print sum(sys.getsizeof(t) for t in tokens)
# 7067732908 bytes for the actual tokens
我预计内存使用量大约为文件大小+列表开销= 1.5 GB。为什么令牌在加载到列表中时会占用更多内存?
答案 0 :(得分:4)
有两个原因:
CPython中的每个字符串在其C对象头中都有相当多的样板文件;在Python 2 64位系统上,空unicode
对象使用52个字节,这是每个 unicode
对象的固定开销,在您计算之前它包含的数据。如果您有1.14M unicode
个对象(不像u''
这样的单个对象),那么您仅在每个对象的开销上使用近6 GB。
您正在使用Python 2和decode
从str
到unicode
,这取决于您的Python 2的构建配置,使用固定的2或4每个字符的字节数,即使是纯ASCII字符串;根据您的数字,您将使用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),而不是两个或四个(和Basic Multilingual Plane中的任何内容都将使用每个字符两个字节,而不是四个;只有非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字节/ char unicode
编译,Py3上只有62个字节。
或者,作为一个廉价的黑客,当你知道它的纯ASCII时,你可以尝试在Py2上从unicode
转换回str
;在Py2上,这两种类型在很大程度上是可互操作的,str
具有较小的每对象开销(37字节对52),并且只使用一个字节/ char。手动从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
的动机的很大一部分。