python内存用法:txt文件比包含文件文本的python列表小得多

时间:2017-07-27 02:05:33

标签: python python-2.7 memory utf-8 nlp

我有一个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。为什么令牌在加载到列表中时会占用更多内存?

1 个答案:

答案 0 :(得分:4)

有两个原因:

  1. CPython中的每个字符串在其C对象头中都有相当多的样板文件;在Python 2 64位系统上,空unicode对象使用52个字节,这是每个 unicode对象的固定开销,在您计算之前它包含的数据。如果您有1.14M unicode个对象(不像u''这样的单个对象),那么您仅在每个对象的开销上使用近6 GB。

    < / LI>
  2. 您正在使用Python 2和decodestrunicode,这取决于您的Python 2的构建配置,使用固定的2或4每个字符的字节数,即使是纯ASCII字符串;根据您的数字,您将使用4字节/字符系统。因此,除了对象头开销超过543 MB的数据外,它需要超过2 GB。

  3. 标题问题在很大程度上是不可克服的(Python对象总是会在标题上浪费几十个字节);每个Python对象都有很高的固定开销(如上所述,我的x64系统上的sys.getsizeof(u'')为52,尽管只存储了8个字节的&#34;真实&#34;数据,str&#39;长度)。

    但是由于你的输入主要是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中分离bytesstr的动机的很大一部分。