使用Pickle / cPickle达到最大递归深度

71 投票
6 回答
56306 浏览
提问于 2025-04-15 18:27

背景:我正在构建一个字典的前缀树(trie),使用一种简单的构建算法。输入的列表包含430万个utf-8字符串,并且这些字符串是按字典顺序排列的。最终生成的图是无环的,最大深度为638个节点。我的脚本第一行通过sys.setrecursionlimit()将递归限制设置为1100。

问题:我希望能够将我的前缀树保存到磁盘上,这样我就可以在需要时直接加载到内存中,而不必从头开始重建(大约需要22分钟)。我尝试了pickle.dump()cPickle.dump(),使用了文本和二进制两种协议。每次操作后,我都会得到一个类似下面的错误信息:

  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 649, in save_dict
    self._batch_setitems(obj.iteritems())
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 663, in _batch_setitems
    save(v)
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 725, in save_inst
    save(stuff)
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 648, in save_dict
    self.memoize(obj)
RuntimeError: maximum recursion depth exceeded

我的数据结构相对简单:trie包含一个起始状态的引用,并定义了一些方法。dfa_state包含一个布尔字段、一个字符串字段,以及一个从标签到状态的字典映射。

我对pickle的内部工作原理不是很了解,难道我的最大递归深度需要大于或等于前缀树深度的n倍吗?还是说这可能是由于我不知道的其他原因造成的?

更新:将递归深度设置为3000并没有帮助,所以这个方向看起来不太有希望。

更新2:你们说得对;我之前太短视了,以为pickle会因为默认的递归限制而使用较小的嵌套深度。设置为10,000就解决了问题。

6 个回答

10

为了防止程序崩溃,栈的大小也必须通过 resource.setrlimit 来增加

如果你只使用 sys.setrecursionlimit,当达到Linux内核允许的最大栈大小时,仍然可能会导致程序崩溃。

这个值可以通过 resource.setrlimit 来增加,具体可以参考这里: 在Python脚本中设置栈大小

import pickle
import resource
import sys

print resource.getrlimit(resource.RLIMIT_STACK)
print sys.getrecursionlimit()

max_rec = 0x100000

# May segfault without this line. 0x100 is a guess at the size of each stack frame.
resource.setrlimit(resource.RLIMIT_STACK, [0x100 * max_rec, resource.RLIM_INFINITY])
sys.setrecursionlimit(max_rec)

a = []
# 0x10 is to account for subfunctions called inside `pickle`.
for i in xrange(max_rec / 0x10):
    a = [a]
print pickle.dumps(a, -1)

另外可以查看: Python中的最大递归深度是多少,如何增加它?

对我来说,默认的最大值是8MB。

在Ubuntu 16.10和Python 2.7.12上测试过。

11

Pickle确实需要递归地遍历你的字典树。如果Pickle只用了5层函数调用来完成工作,而你的字典树深度有638层,那就需要把层数设置到3000以上。

试试一个更大的数字,递归限制其实是为了保护用户,避免在递归过程中陷入无限循环而让人等得太久。

Pickle对循环的处理还不错,所以即使你的字典树里有循环也没关系。

48

来自文档的说明:

如果你尝试保存一个非常复杂的递归数据结构,可能会超过最大递归深度,这时会出现一个运行时错误(RuntimeError)。你可以通过 sys.setrecursionlimit() 来小心地提高这个限制。

虽然你的字典树(trie)实现可能很简单,但它使用了递归,这在转换为持久化数据结构时可能会出现问题。

我建议你继续提高递归限制,看看你正在处理的数据和使用的字典树实现是否有一个上限。

除此之外,如果可能的话,你可以尝试将你的树结构实现得“少一些递归”,或者编写一个额外的实现,使其内置数据持久化(在你的实现中使用pickle和shelves)。希望这对你有帮助。

撰写回答