65

背景:我正在使用最小构造算法构建一个表示字典的树。输入列表是 4.3M utf-8 字符串,按字典顺序排序。结果图是非循环的,最大深度为 638 个节点。我的脚本的第一行通过 .将递归限制设置为 1100 sys.setrecursionlimit()

问题:我希望能够将我的尝试序列化到磁盘,这样我就可以将它加载到内存中,而无需从头开始重建(大约 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 的特里深度的 n 倍?或者这可能是由我不知道的其他原因引起的?

更新: 将递归深度设置为 3000 并没有帮助,所以这条途径看起来并不乐观。

更新 2: 你们是对的;由于默认递归限制,我假设 pickle 将使用较小的嵌套深度是短视的。10,000 人成功了。

4

6 回答 6

43

文档

尝试腌制高度递归的数据结构可能会超过最大递归深度,在这种情况下会引发 RuntimeError。您可以使用 小心地提高此限制sys.setrecursionlimit()

尽管您的 trie 实现可能很简单,但它使用递归并且在转换为持久数据结构时可能会导致问题。

我的建议是继续提高递归限制,以查看您正在使用的数据和您正在使用的 trie 实现是否存在上限。

除此之外,如果可能的话,您可以尝试将树实现更改为“更少递归”,或者编写一个内置数据持久性的附加实现(在您的实现中使用泡菜和货架)。希望有帮助

于 2010-01-25T19:58:23.177 回答
11

Pickle 确实需要递归地遍历您的尝试。如果 Pickle 仅使用 5 个级别的函数调用来完成工作,则深度 638 的尝试将需要将级别设置为 3000 以上。

尝试一个更大的数字,递归限制实际上只是为了保护用户在递归陷入无限洞时不必等待太久。

Pickle 可以处理循环,所以即使你的 trie 有循环也没关系

于 2010-01-25T21:28:36.217 回答
7

堆栈大小也必须增加resource.setrlimit以防止段错误

如果您使用 just 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)

另请参阅:Python 中的最大递归深度是多少,以及如何增加它?

我的默认最大值是 8Mb。

在 Ubuntu 16.10、Python 2.7.12 上测试。

于 2017-01-29T00:05:15.450 回答
5

仔细检查您的结构确实是无环的。

您可以尝试进一步提高限制。有一个硬最大值取决于平台,但尝试 50000 是合理的。

还可以尝试腌制你的特里的一个微不足道的小版本。如果 pickle 死了,即使它只存储了几个三个字母的单词,那么你知道你的 trie 而不是 pickle 存在一些基本问题。但是,如果仅在您尝试存储 10k 个单词时发生,则可能是 pickle 平台限制的错误。

于 2010-01-25T19:58:42.550 回答
0

对我来说,删除所有用途importlib.reload解决了这个问题。我什至不需要增加限制setrecursionlimit

如果您想知道我是如何找到它的,请继续阅读。

在我找到解决方案之前,我发现如果我先将模型移到 CPU 上,我实际上可以保存模型,但是在评估期间出现错误(XXX 是类名,这无关紧要):

PicklingError: Can't pickle <class 'XXX'>: it's not the same object as XXX

然后我找到了这个答案: https ://stackoverflow.com/a/1964942/4295037

但是在删除所有用途后,importlib.reload我能够保存模型而无需先将其移动到 CPU 设备。

于 2020-08-16T21:35:08.350 回答
0

我的需求有点紧迫,所以我通过将字典保存为 .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 格式导出字典。

于 2017-09-25T22:14:52.913 回答