Comment éviter les calculs chaque fois qu'un module python est rechargé

StackOverflow https://stackoverflow.com/questions/195626

  •  10-07-2019
  •  | 
  •  

Question

J'ai un module python qui utilise une énorme variable globale du dictionnaire. Actuellement, je place le code de calcul dans la section supérieure. Chaque importation ou rechargement du module prend plus d'une minute, ce qui est totalement inacceptable. Comment puis-je enregistrer le résultat du calcul quelque part afin que le prochain import / rechargement ne le calcule pas? J'ai essayé cPickle, mais charger la variable dictionnaire à partir d'un fichier (1,3M) prend environ le même temps que le calcul.

Pour plus d'informations sur mon problème,

FD = FreqDist(word for word in brown.words()) # this line of code takes 1 min
Était-ce utile?

La solution

Juste pour clarifier: le code dans le corps d'un module n'est pas exécuté à chaque fois que le module est importé - il est exécuté une seule fois, après quoi les importations futures retrouvent le module déjà créé, plutôt que le recréer. Consultez sys.modules pour voir la liste des modules mis en cache.

Cependant, si votre problème est lié au temps requis pour la première importation après l'exécution du programme, vous devrez probablement utiliser une méthode autre qu'un dict Python. Le mieux serait probablement d’utiliser un formulaire sur disque, par exemple une base de données sqlite, l’un des modules de dbm.

Pour un changement minimal de votre interface, le module shelve peut être votre meilleure option. Cela crée une interface assez transparente entre les modules dbm, qui les fait se comporter comme des dictées python arbitraires, permettant de stocker toute valeur pouvant être sélectionnée. Voici un exemple:

# Create dict with a million items:
import shelve
d = shelve.open('path/to/my_persistant_dict')
d.update(('key%d' % x, x) for x in xrange(1000000))
d.close()

Ensuite, utilisez le processus suivant. Il ne devrait y avoir aucun délai important, car les recherches ne sont effectuées que pour la clé demandée sur le formulaire sur disque, de sorte que tout ne soit pas chargé en mémoire:

>>> d = shelve.open('path/to/my_persistant_dict')
>>> print d['key99999']
99999

Il est un peu plus lent qu'un vrai dict, et le chargement prendra encore beaucoup de temps si vous faites quelque chose qui nécessite toutes les touches (par exemple, essayez de l'imprimer), mais peut résoudre votre problème.

Autres conseils

Calculez votre variable globale lors de la première utilisation.

class Proxy:
    @property
    def global_name(self):
        # calculate your global var here, enable cache if needed
        ...

_proxy_object = Proxy()
GLOBAL_NAME = _proxy_object.global_name

Ou mieux encore, accédez aux données nécessaires via un objet de données spécial.

class Data:
    GLOBAL_NAME = property(...)

data = Data()

Exemple:

from some_module import data

print(data.GLOBAL_NAME)

Voir les paramètres Django .

Je suppose que vous avez collé le dictel littéral dans la source, et c'est ce qui prend une minute? Je ne sais pas comment contourner cela, mais vous pourriez probablement éviter d'instancier ce dict sur importer ... Vous pourriez l'instancier paresseusement la première fois qu'il est réellement utilisé.

Vous pouvez essayer d'utiliser le module marshal au lieu du c ? Pickle un; ça pourrait être plus rapide. Python utilise ce module pour stocker les valeurs dans un format binaire. Notez en particulier le paragraphe suivant, pour voir si marshal répond à vos besoins:

  

Tous les types d'objet Python ne sont pas pris en charge. en général, seuls les objets dont la valeur est indépendante d'un appel particulier de Python peuvent être écrits et lus par ce module. Les types suivants sont pris en charge: Aucun, entier, entier long, nombre à virgule flottante, chaîne de caractères, objet Unicode, n-uplets, listes, ensembles, dictionnaires et objets de code, sachant que les n-uplets, les listes et les dictionnaires sont uniquement pris en charge tant que comme les valeurs qui y sont contenues sont elles-mêmes supportées; et les listes et les dictionnaires récursifs ne doivent pas être écrits (ils provoqueront des boucles infinies).

Pour plus de sécurité, avant de démystifier le dict, assurez-vous que la version Python qui démarque le dict est identique à celle qui a été utilisée par le maréchal, car rien ne garantit la compatibilité avec les versions antérieures.

Si la solution "shelve" s'avère trop lente ou fastidieuse, il existe d'autres possibilités:

shelve devient très lent avec les grands ensembles de données. J'utilise redis assez et a écrit un enveloppe FreqDist autour de celle-ci. C'est très rapide et on peut y accéder simultanément.

Vous pouvez utiliser un shelve pour stocker vos données sur disque au lieu de charger la totalité des données en mémoire. Le temps de démarrage sera donc très rapide, mais le compromis sera un temps d’accès plus lent.

Shelve prendra également en compte les valeurs dict, mais ne le fera pas au démarrage pour tous les éléments, mais uniquement au moment de l'accès à chacun d'eux.

Quelques éléments qui contribueront à accélérer les importations:

  1. Vous pouvez essayer d’exécuter python avec l’option -OO lors de l’exécution de python. Cela fera quelques optimisations qui réduiront le temps d’importation des modules.
  2. Y a-t-il une raison pour laquelle vous ne pourriez pas diviser le dictionnaire en dictionnaires plus petits dans des modules distincts pouvant être chargés plus rapidement?
  3. En dernier recours, vous pouvez effectuer les calculs de manière asynchrone afin qu'ils ne retardent pas votre programme jusqu'à ce qu'il ait besoin des résultats. Ou peut-être même mettre le dictionnaire dans un processus séparé et échanger des données en utilisant IPC si vous souhaitez tirer parti des architectures multicœurs.

Cela dit, je conviens que vous ne devriez pas rencontrer de retard dans l'importation de modules après la première importation. Voici quelques autres réflexions générales:

  1. Importez-vous le module dans une fonction? Si tel est le cas, cela peut donner lieu à des problèmes de performances, car il doit vérifier si le module est chargé à chaque frappe dans l'instruction d'importation.
  2. Votre programme est-il multithread? J'ai déjà vu des exemples où l'exécution de code lors de l'importation de modules dans une application multithread peut être source d'inquiétudes et d'instabilité d'application (notamment avec le module cgitb).
  3. S'il s'agit d'une variable globale, sachez que les temps de recherche d'une variable globale peuvent être considérablement plus longs que les temps de recherche d'une variable locale. Dans ce cas, vous pouvez obtenir une amélioration significative des performances en liant le dictionnaire à une variable locale si vous l’utilisez plusieurs fois dans le même contexte.

Cela dit, il est un peu difficile de vous donner un conseil spécifique sans un peu plus de contexte. Plus précisément, où l'importez-vous? Et quels sont les calculs?

  1. Factorisez la partie informatique intensive dans un module séparé. Alors au moins lors du rechargement, vous n'aurez pas à attendre.

  2. Essayez de vider la structure de données à l'aide du protocole 2. La commande à essayer serait cPickle.dump (FD, protocole = 2) . À partir de la docstring pour cPickle.Pickler :

    Protocol 0 is the
    only protocol that can be written to a file opened in text
    mode and read back successfully.  When using a protocol higher
    than 0, make sure the file is opened in binary mode, both when
    pickling and unpickling. 
    

Je traverse ce même problème ... shelve, bases de données, etc ... sont trop lents pour ce type de problème. Vous aurez besoin de prendre le coup une fois, insérez-le dans un magasin de clé / val en mémoire comme Redis. Il vivra simplement là-bas en mémoire (en avertissant qu'il pourrait utiliser une bonne quantité de mémoire, vous voudrez peut-être une boîte dédiée). Vous n'aurez jamais à le recharger et vous n'aurez plus qu'à chercher dans la mémoire les clés

r = Redis()
r.set(key, word)

word = r.get(key)

Pour développer l'idée de calcul différé, pourquoi ne pas transformer le dict en une classe qui fournit (et met en cache) les éléments nécessaires?

Vous pouvez également utiliser psyco pour accélérer l'exécution globale ...

OU vous pourriez simplement utiliser une base de données pour stocker les valeurs? Découvrez SQLObject, ce qui facilite grandement le stockage de données dans une base de données.

Il existe une autre solution assez évidente à ce problème. Lorsque le code est rechargé, l'étendue d'origine est toujours disponible.

Alors ... faire quelque chose comme ça va faire en sorte que ce code ne soit exécuté qu'une seule fois.

try:
    FD
except NameError:
    FD = FreqDist(word for word in brown.words())
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top