编译为字节代码会占用太多内存

时间:2011-07-29 23:38:14

标签: python memory dictionary bytecode

我需要将一个非常大的字典导入到python中,并且我遇到了一些意想不到的内存瓶颈。字典的格式为

d = {(1,2,3):(1,2,3,4), (2,5,6)=(4,2,3,4,5,6), ... }

所以每个键都是一个3元组,每个值都是一个相对较小的任意大小的元组(可能永远不会超过30个元素)。使字典变大的原因是键的数量。我正在使用的一个较小的例子大约有247257个键。我通过模拟生成这个字典,所以我可以写出一个定义这个字典的文本文件,对于我刚刚提到的例子,这是一个94MB的文件。我遇到的瓶颈是初始编译为python字节代码会占用大约14GB的内存。所以我第一次导入字典时,我看到RAM使用率上升了,经过10秒后,所有内容都被加载了。如果已生成.pyc文件,则导入几乎是即时的。使用pympler,我已经确定这个字典在内存中只有大约200 MB。这是什么交易?关于如何将此字典加载到python或至少编译为字节代码,我还有其他选项吗?我正在用C ++运行生成模拟,我无法以任何我需要的格式编写文件。那里有没有选项(python库等)?我正在与一些需要这些数据作为字典的软件接口,所以请不要在该领域提出其他建议。另外,万一你想知道,我已经在文本文件中定义了字典,就像上面的定义一样,

d = {}
d[1,2,3] = (1,2,3,4)
d[2,5,6] = (4,2,3,4,5,6)
...

两者都在编译到字节代码时给出相同的内存峰值。事实上,第二个似乎稍差,这对我来说是令人惊讶的。必须有一些方法来驯服初始编译需要的ram数量。看起来它应该能够以某种方式一次编译一个键值对。有什么想法吗?

其他信息: 使用python 2.6.5

7 个答案:

答案 0 :(得分:3)

我想问题是,在解析文件时,会为每个元素添加一个巨大的语法树,但开销很小。生成字节码后,不再需要和转储语法树,从而产生200MB的数据。

您是否尝试使用以下格式将数据存储在单独的文件中,然后在python中动态加载?

1,2,3=1,2,3
2,5,6=4,2,3,4,5,6

Python脚本应如下所示:

file = open("filename")
d = {}

for line in file:
    key, val = line.split("=")
    key = tuple(key.split(","))
    d[key] = tuple(val.split(","))

file.close()

答案 1 :(得分:2)

答案 2 :(得分:1)

我猜你在执行“import module_containing_humungous_dict_statement”时会出现大的编译峰值。然后,如果您只有一个语句或247257个单独的赋值语句,则无关紧要,整个模块仍将立即编译。您可以尝试使用单独的赋值语句表单,然后打开文件,一次读取一行,然后执行它。那么你一次只会编译一行。可能需要一段时间。

答案 3 :(得分:0)

我怀疑创建要用作密钥的列表是多么昂贵。定义一个函数,它将三元组的三个部分作为输入并返回一个管道分隔的字符串。用它作为你的钥匙。

答案 4 :(得分:0)

我读你的问题的方式是,你在你的模拟器中生成Python源,并且生成的源具有硬编码的巨型字典的内容。如果这是真的,那么你可以很容易地生成这个:

def giantdict():
  d0 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # first 1000 key/value pairs here
  d1 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  d2 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  d3 = {(1, 2): (3, 4), (3, 4): (5, 6), ...}  # next 1000 key/value pairs
  # ... until you're done
  bigd = d0
  bigd.update(d1)
  del d1
  bigd.update(d2)
  del d2
  # ... continue updating with all the dN dictionaries
  return bigd

我不确定这会改善编译时间,但是尝试一下。如果在编译时将所有内容放在一个数据结构中会受到惩罚,那么将其拆分并在运行时组装这些部分可能会解决它。

虽然这种代码(我的或你的代码)会引起我的愤怒和愤怒,如果一个人写下它,我认为没有必要生成的代码“好”,只要你知道没有人需要阅读或维护它。

答案 5 :(得分:0)

这是一个使用defaultdict自动嵌套索引值的类,使用一些特殊的__getitem____setitem__方法接受元组作为参数:

from collections import defaultdict

defdict3level = (lambda : defaultdict(lambda : 
                            defaultdict( lambda : 
                                defaultdict(tuple))))

class dict3level(object):
    def __init__(self):
        self.defdict = defdict3level()

    def __getitem__(self, key):
        if isinstance(key, tuple):
            if len(key)==3:
                return self.defdict[key[0]][key[1]][key[2]]
            elif len(key)==2:
                return self.defdict[key[0]][key[1]]
            elif len(key)==1:
                return self.defdict[key[0]]
        else:
            return self.defdict[key]

    def __setitem__(self, key, value):
        if isinstance(key, tuple) and len(key)==3:
            self.defdict[key[0]][key[1]][key[2]] = value
        else:
            self.defdict[key] = value

    def __getattr__(self, attr):
        return getattr(self.defdict, attr)

现在执行你之前的所有作业:

d = dict3level()
d[1,2,3] = (1,2,3,4)
d[1,2,7] = (3,4,5,6)
d[2,5,6] = (4,2,3,4,5,6)

您仍然可以获得特定元组的特定条目:

# get a specific entry
print d[1,2,3]

但你也可以按级别导航你的词典:

# get all different 0'th index values
print d.keys()

# get all sub values in d[1,2,*]
print d[1,2].keys()
for key in d[1,2]:
    print "d[1,2,%d] = %s" % (key, d[1,2][key])

# no such entry, return empty tuple
print d[1,2,0]

给出:

print d[1,2,3] -> (1, 2, 3, 4)
print d.keys() -> [1, 2]
print d[1,2].keys() -> [3, 7]
for key in d[1,2]:... -> 
    d[1,2,3] = (1, 2, 3, 4)
    d[1,2,7] = (3, 4, 5, 6)
print d[1,2,0] -> ()

(不知道这会如何影响你的记忆和/或酸洗问题,但最终的结构有更多的能力。)

答案 6 :(得分:0)