web-dev-qa-db-fra.com

Frapper la profondeur maximale de récursivité avec Pickle/cPickle

Le fond: je construis un trie pour représenter un dictionnaire, en utilisant un algorithme de construction minimal. La liste de saisie contient 4,3 millions de chaînes utf-8, triées lexicographiquement. Le graphique obtenu est acyclique et a une profondeur maximale de 638 nœuds. La première ligne de mon script définit la limite de récursivité à 1100 via sys.setrecursionlimit().

Le problème: j'aimerais pouvoir sérialiser mon test sur disque afin de pouvoir le charger en mémoire sans avoir à reconstruire à partir de zéro (environ 22 minutes). J'ai essayé à la fois pickle.dump() et cPickle.dump(), avec les protocoles texte et binaire. Chaque fois, je reçois une trace de pile qui ressemble à ce qui suit:

  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

Mes structures de données sont relativement simples: trie contient une référence à un état de départ et définit certaines méthodes. dfa_state contient un champ booléen, un champ chaîne et un dictionnaire mappant d'une étiquette à une autre.

Je ne connais pas très bien le fonctionnement interne de pickle - ma profondeur de récursion maximale doit-elle être supérieure/égale à n fois la profondeur du test pour certains n? Ou cela pourrait-il être causé par quelque chose d'autre dont je ne suis pas au courant?

Mise à jour: Le réglage de la profondeur de récursivité sur 3000 n'a pas aidé, cette avenue n'a donc pas l'air prometteuse.

Mise à jour 2: Vous aviez raison; J'étais à courte vue en supposant que pickle utiliserait une faible profondeur de nidification en raison des limitations de récursivité par défaut. 10 000 ont fait le tour.

46
danben

De les docs :

Essayer de décaper une structure de données hautement récursive peut dépasser la profondeur de récursivité maximale, une erreur RuntimeError sera générée dans ce cas. Vous pouvez soigneusement augmenter cette limite avec sys.setrecursionlimit().

Bien que votre implémentation soit simple, elle utilise la récursion et peut entraîner des problèmes lors de la conversion en structure de données persistante. 

Ma recommandation serait de continuer à augmenter la limite de récursivité pour voir s'il existe une limite supérieure pour les données avec lesquelles vous travaillez et la mise en œuvre que vous utilisez. 

Sinon, vous pouvez essayer de modifier votre implémentation d'arborescence afin qu'elle soit "moins récursive", si possible, ou d'écrire une implémentation supplémentaire intégrant la persistance des données (utilisez pickles et shelves dans votre implémentation). . J'espère que cela pourra aider

33
Jason Coon

Pickle a besoin de marcher de manière récursive sur votre trie. Si Pickle utilise seulement 5 niveaux d'appels de fonction pour effectuer le travail, votre niveau de profondeur 638 aura besoin d'un niveau supérieur à 3 000.

Essayez un nombre beaucoup plus grand, la limite de récursivité est vraiment là pour éviter aux utilisateurs de trop attendre si la récursion tombe dans un trou infini.

Pickle gère les cycles ok, donc peu importe même si votre trie avait un cycle

9
John La Rooy

La taille de la pile doit également être augmentée avec resource.setrlimit pour éviter les erreurs de segmentation

Si vous utilisez uniquement sys.setrecursionlimit, vous pouvez toujours utiliser segfault si vous atteignez la taille de pile maximale autorisée par le noyau Linux.

Cette valeur peut être augmentée avec resource.setrlimit comme indiqué à: Définition de stacksize dans un script 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)

Voir aussi: Quelle est la profondeur de récursivité maximale en Python et comment l'augmenter?

La valeur maximale par défaut pour moi est de 8 Mo.

Testé sur Ubuntu 16.10, Python 2.7.12.

Vérifiez que votre structure est bien acyclique.

Vous pouvez essayer d'augmenter la limite encore plus loin. Il y a un maximum difficile qui dépend de la plate-forme, mais essayer 50000 serait raisonnable.

Essayez également de choisir une version trivialement petite de votre test. Si pickle meurt alors que vous ne stockez que quelques mots de trois lettres, alors vous savez qu'il existe un problème fondamental avec votre test et non le pickle Mais si cela ne se produit que lorsque vous essayez de stocker 10 000 mots, cela peut être dû à une limitation de la plate-forme en mode pickle.

3
Cerin

Mes besoins étaient quelque peu immédiats et j'ai donc résolu ce problème en enregistrant mon dictionnaire au format .txt. La seule chose à faire est que lorsque vous rechargez votre fichier, vous devez le reconvertir en dictionnaire.

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))

Si cela ne fonctionne pas, essayez d’exporter le dictionnaire au format json.

0
Alexandre