J'essaie de charger quelques fichiers dans la mémoire. Les fichiers ont l'un des 3 formats suivants:
En effet, ce sont des fichiers statiques ngram, au cas où cela aiderait à la solution. Par exemple:
i_love TAB 10
love_you TAB 12
Actuellement, le pseudocode que je fais en ce moment est
loadData(file):
data = {}
for line in file:
first, second = line.split('\t')
data[first] = int(second) #or float(second)
return data
À ma grande surprise, alors que la taille totale des fichiers sur le disque est d'environ 21 Mo, une fois chargé en mémoire, le processus prend 120 à 180 Mo de mémoire! (l'ensemble python ne charge aucune autre donnée en mémoire).
Il y a moins de 10 fichiers, la plupart d'entre eux resteraient stables à environ 50 à 80 000 lignes, à l'exception d'un fichier qui compte actuellement des millions de lignes.
Je voudrais donc demander une technique/structure de données pour réduire la consommation de mémoire:
Merci beaucoup. J'attends vos conseils avec impatience.
Je ne peux pas offrir une stratégie complète qui aiderait à améliorer l'empreinte mémoire, mais je pense que cela peut aider à analyser ce qui prend exactement autant de mémoire.
Si vous regardez la implémentation Python du dictionnaire (qui est une implémentation relativement simple d'une table de hachage), ainsi que l'implémentation de la construction -dans les types de données chaîne et entier, par exemple ici (spécifiquement object.h, intobject.h, stringobject.h et dictobject.h, ainsi que les fichiers * .c correspondants dans ../Objects ), vous pouvez calculer avec une certaine précision l'espace requis:
Un entier est un objet de taille fixe, c'est-à-dire qu'il contient un compte de référence, un pointeur de type et l'entier réel, au total généralement au moins 12 octets sur un système 32 bits et 24 octets sur un système 64 bits, sans tenir compte compte de l'espace supplémentaire éventuellement perdu par l'alignement.
Un objet chaîne est de taille variable, ce qui signifie qu'il contient
au total au moins 24 octets sur 32 bits ou 60 octets sur 64 bits, n'inclut pas l'espace pour la chaîne elle-même.
Le dictionnaire lui-même se compose d'un certain nombre de compartiments, chacun contenant
au total au moins 12 octets sur 32 bits et 24 octets sur 64 bits.
Le dictionnaire commence par 8 compartiments vides et est redimensionné en doublant le nombre d'entrées chaque fois que sa capacité est atteinte.
J'ai effectué un test avec une liste de 46 461 chaînes uniques (337 670 octets de taille de chaîne concaténée), chacune associée à un entier - similaire à votre configuration, sur une machine 32 bits. Selon le calcul ci-dessus, je m'attendrais à une empreinte mémoire minimale de
au total 2,65 Mo. (Pour un système 64 bits, le calcul correspondant donne 5,5 Mo.)
Lors de l'exécution de l'interpréteur Python inactif, son encombrement en fonction de l'outil ps
- est de 4,6 Mo. La consommation totale de mémoire attendue après la création du dictionnaire est donc d'environ 4,6 + 2,65 = 7,25 Mo. L'empreinte mémoire réelle (selon ps
) dans mon test était 7,6 Mo. Je suppose que les 0,35 Mo supplémentaires ont été consommés par les frais généraux générés par la stratégie d'allocation de mémoire de Python (pour les arénas de mémoire, etc. .)
Bien sûr, beaucoup de gens vont maintenant souligner que mon utilisation de ps
pour mesurer l'empreinte mémoire est inexacte et mes hypothèses sur la taille des types de pointeurs et des entiers sur les systèmes 32 bits et 64 bits peuvent être erronées sur de nombreux systèmes spécifiques. Accordé.
Mais, néanmoins, les principales conclusions , je crois, sont les suivantes:
1) SQLite en mémoire semble être une excellente solution, il vous permettra d'interroger vos données plus facilement une fois chargées, ce qui est un plaisir
sqlite3.connect (': mémoire:')
2) vous voulez probablement un Tuple nommé - je suis sûr qu'ils sont plus légers que les dictionnaires et vous pouvez accéder aux propriétés en utilisant la notation par points (pour laquelle j'ai une préférence esthétique de toute façon).
http://docs.python.org/dev/library/collections
3) vous voudrez peut-être jeter un œil à Redis: https://github.com/andymccurdy/redis-py
Il est RAPIDE et vous permettra de persister facilement, ce qui signifie que vous n'avez pas à charger l'ensemble entier à chaque fois que vous souhaitez l'utiliser.
4) un trie semble être une bonne idée, mais ajoute une certaine complexité théorique à la fin de votre travail. Vous pouvez utiliser Redis pour l'implémenter et le stocker, ce qui augmentera encore plus votre vitesse.
Mais dans l'ensemble, les tuples nommés sont probablement l'astuce ici.
Dans le disque, vous n'avez que des chaînes, lors du chargement dans Python l'interpréteur doit créer une structure entière pour chaque chaîne et pour chaque dictionnaire, en plus de la chaîne elle-même.
Il n'y a aucun moyen de réduire la mémoire utilisée par les dict, mais il existe d'autres façons d'aborder le problème. Si vous souhaitez échanger de la vitesse contre de la mémoire, vous devriez envisager de stocker et d'interroger les chaînes d'un fichier SQLite au lieu de tout charger dans les dictionnaires en mémoire.
Cela ressemble à une structure de données Trie ( http://en.m.wikipedia.org/wiki/Trie ) pourrait mieux répondre à votre désir d'efficacité de la mémoire.
Mise à jour: l'efficacité de la mémoire de python dict a été soulevée comme un problème, bien qu'elle ait été rejetée de la bibliothèque standard compte tenu de la disponibilité de bibliothèques tierces. Voir: http: // bugs.python.org/issue952
Vous pouvez remplacer dict par blist.sorteddict pour l'accès au journal (n) sans surcharge de mémoire. C'est pratique car il se comporte exactement comme un dictionnaire, c'est-à-dire qu'il implémente toutes ses méthodes, vous n'avez donc qu'à changer le type initial.
Si vous essayez de stocker des données numériques de manière compacte dans python en mémoire, votre meilleure solution est probablement Numpy.
Numpy ( http://numpy.org ) alloue des structures de données à l'aide de structures C natives. La plupart de ses structures de données supposent que vous stockez un seul type de données, donc ce n'est pas pour toutes les situations (vous devrez peut-être stocker null, etc.), mais il peut être très, très, très rapide et est à peu près aussi compact que vous pourriez demander. Beaucoup de science se fait avec (voir aussi: SciPy).
Bien sûr, il existe une autre option: zlib , si vous avez:
Vous pouvez simplement déclarer une "page" de données (quelle que soit la taille de votre choix) en tant que tableau, lire les données, les stocker dans le tableau, les compresser, puis lire d'autres données jusqu'à ce que vous ayez toutes les données en mémoire vouloir.
Ensuite, parcourez les pages, décompressez, reconvertissez en tableau et effectuez vos opérations selon les besoins. Par exemple:
def arrayToBlob(self, inArray):
a = array.array('f', inArray)
return a.tostring()
def blobToArray(self, blob, suppressWarning=False):
try:
out = array.array('f', [])
out.fromstring(blob)
except Exception, e:
if not suppressWarning:
msg = "Exception: blob2array, err: %s, in: %s" % (e, blob)
self.log.warning(msg)
raise Exception, msg
return out
Une fois que vous avez des données en tant qu'objet blob, vous pouvez transmettre cet objet blob à zlib et compresser les données. Si vous avez beaucoup de valeurs répétées, ce blob peut être considérablement compressé.
Bien sûr, c'est plus lent que de tout garder non compressé, mais si vous ne pouvez pas le garder en mémoire, vos choix sont limités pour commencer.
Même avec la compression, il se peut que tout ne tienne pas en mémoire, auquel cas vous devrez peut-être écrire les pages compressées sous forme de chaînes ou de cornichons, etc.
Bonne chance!