web-dev-qa-db-ja.com

Pickle / cPickleを使用して最大再帰深度に到達する

背景:最小限の構築アルゴリズムを使用して、辞書を表すトライを作成しています。入力リストは、辞書順でソートされた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には、ブールフィールド、文字列フィールド、およびラベルから状態への辞書マッピングが含まれます。

私はpickleの内部動作にあまり詳しくありません-最大再帰の深さは、いくつかのnのトライの深さのn倍以上である必要がありますか?または、これは私が知らない何かによって引き起こされるのでしょうか?

pdate:再帰の深さを3000に設定しても効果がないため、この方法は有望ではありません。

更新2:君たちは正しかった。デフォルトの再帰の制限により、pickleは小さなネスト深度を使用すると想定していたため、近視眼的でした。 10,000はトリックをしました。

54
danben

the docs から:

非常に再帰的なデータ構造をpickle化しようとすると、最大の再帰の深さを超える可能性があります。この場合、RuntimeErrorが発生します。 sys.setrecursionlimit()を使用して、この制限を慎重に引き上げることができます。

トライの実装は単純かもしれませんが、再帰を使用しているため、永続的なデータ構造に変換するときに問題が発生する可能性があります。

私が推奨するのは、再帰制限を引き続き引き上げて、作業しているデータと使用しているトライの実装に上限があるかどうかを確認することです。

それ以外の場合は、可能であれば、ツリーの実装を「再帰的でないもの」に変更するか、データの永続性が組み込まれた追加の実装を作成してください(ピクルと-を使用) あなたの実装で)。それが役に立てば幸い

36
Jason Coon

ピクルは再帰的にトライを歩く必要があります。 Pickleが5レベルの関数呼び出しを使用して作業を行っている場合、深度638のトライではレベルを3000以上に設定する必要があります。

はるかに大きな数を試してください。再帰の制限は、再帰が無限の穴に落ちた場合にユーザーが長時間待機しなくて済むようにするために実際にあります。

ピクルはサイクルを処理できるので、トライにサイクルがあったとしても問題ありません

9
John La Rooy

segfaultを防ぐために、スタックサイズもresource.setrlimitで増やす必要があります

sys.setrecursionlimitのみを使用する場合でも、Linuxカーネルで許可されている最大スタックサイズに達しても、segfaultを実行できます。

この値は、次のようにresource.setrlimitで増やすことができます 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。

構造が本当に非環状であることを再確認してください。

限界をさらに上げることもできます。プラットフォームに依存するハード最大値がありますが、50000を試すのが妥当です。

また、小さなバージョンのトライを漬けてみてください。 3文字の単語しか格納されていないのにピクルスが死亡した場合、ピクルスではなく、トライにいくつかの基本的な問題があることがわかります。しかし、1万語を保存しようとしたときにのみ発生する場合は、pickleのプラットフォーム制限のせいかもしれません。

3
Cerin

私のニーズはいくぶん差し迫っていたので、辞書を.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形式を使用して辞書をエクスポートしてみてください。

0
Alexandre