使用 Pickle / cPickle 达到最大递归深度
Posted
技术标签:
【中文标题】使用 Pickle / cPickle 达到最大递归深度【英文标题】:Hitting Maximum Recursion Depth Using Pickle / cPickle 【发布时间】:2011-01-09 05:36:48 【问题描述】:背景:我正在使用最小构造算法构建一个表示字典的树。输入列表是 4.3M 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
包含一个布尔字段、一个字符串字段和一个从标签到状态的字典映射。
我对@987654327@ 的内部工作原理不是很熟悉——我的最大递归深度是否需要大于/等于某些 n 的特里树深度的 n 倍?或者这可能是由我不知道的其他原因引起的?
更新:将递归深度设置为 3000 并没有帮助,因此这条途径看起来并不乐观。
更新 2: 你们是对的;由于默认递归限制,我假设 pickle 将使用较小的嵌套深度是短视的。 10,000 人成功了。
【问题讨论】:
【参考方案1】:对我来说,删除 所有 使用 importlib.reload
解决了这个问题。
我确实不需要甚至需要使用setrecursionlimit
增加限制。
如果您想知道我是如何找到它的,请继续阅读。
在找到解决方案之前,我发现如果我先将模型移到 CPU 上,我实际上可以保存模型,但在评估过程中出现错误(XXX 是类名,无关紧要):
PicklingError: Can't pickle <class 'XXX'>: it's not the same object as XXX
然后我找到了这个答案: https://***.com/a/1964942/4295037
但在删除所有使用 importlib.reload
之后,我能够保存模型而无需先将其移动到 CPU 设备。
【讨论】:
【参考方案2】:我的需求有点紧迫,所以我通过将字典保存为 .txt 格式解决了这个问题。唯一的问题是,当您再次加载文件时,您必须将其转换回字典。
import json
# Saving the dictionary
with open('filename.txt', 'w') as file_handle:
file_handle.write(str(dictionary))
# Importing the .txt file
with open('filename.txt', 'r') as file_handle:
f = '"' + file_handle.read() + '"'
# From .txt file to dictionary
dictionary = eval(json.loads(f))
如果这不起作用,您可以尝试使用 json 格式导出字典。
【讨论】:
【参考方案3】:还必须使用resource.setrlimit
增加堆栈大小以防止段错误
如果您只使用sys.setrecursionlimit
,如果达到 Linux 内核允许的最大堆栈大小,您仍然可以发生段错误。
可以使用resource.setrlimit
增加此值,如Setting stacksize in a python script 所述
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)
另见:What is the maximum recursion depth in Python, and how to increase it?
我的默认最大值是 8Mb。
在 Ubuntu 16.10、Python 2.7.12 上测试。
【讨论】:
【参考方案4】:Pickle 确实需要递归地遍历您的尝试。如果 Pickle 仅使用 5 个级别的函数调用来完成工作,那么深度 638 的尝试将需要将级别设置为 3000 以上。
尝试更大的数字,递归限制实际上只是为了保护用户在递归陷入无限洞时不必等待太久。
Pickle 可以处理循环,所以即使你的 trie 有循环也没关系
【讨论】:
如果正确处理循环,它怎么会掉进无限洞? @JohnOptionalSmith,pickle
不会陷入无限洞,但递归在一般情况下可以,因此当递归太深时会引发异常。【参考方案5】:
仔细检查您的结构确实是无环的。
您可以尝试进一步提高限制。有一个硬最大值取决于平台,但尝试 50000 是合理的。
还可以尝试腌制一个很小的 trie 版本。如果 pickle 死了,即使它只存储了几个三个字母的单词,那么你知道你的 trie 而不是 pickle 存在一些基本问题。但是,如果仅在您尝试存储 10k 个单词时发生,则可能是 pickle 平台限制的错误。
【讨论】:
我发现增加递归限制对内存使用有很大影响... svn.python.org/projects/python/trunk/Tools/scripts/…可以帮你找到你的硬件上限 它帮助我缩小了问题的根源。【参考方案6】:来自the docs:
尝试腌制高度递归的数据结构可能会超过最大递归深度,在这种情况下会引发 RuntimeError。您可以使用
sys.setrecursionlimit()
小心提高此限制。
虽然您的 trie 实现可能很简单,但它使用递归并且在转换为持久数据结构时可能会导致问题。
我的建议是继续提高递归限制,以查看您正在使用的数据和您正在使用的 trie 实现是否存在上限。
除此之外,如果可能的话,您可以尝试将树实现更改为“更少递归”,或者编写附加实现内置数据持久性(使用pickles和shelves在您的实施中)。希望有帮助
【讨论】:
以上是关于使用 Pickle / cPickle 达到最大递归深度的主要内容,如果未能解决你的问题,请参考以下文章