web-dev-qa-db-fra.com

Utilisation de la mémoire Python? chargement de grands dictionnaires en mémoire

hé tout, j'ai un fichier sur le disque qui est seulement 168MB. C'est juste une liste de Word séparée par des virgules, id La Parole peut contenir entre 1 et 5 mots. Il y a 6,5 ​​millions de lignes. J'ai créé un dictionnaire en python pour le charger en mémoire afin de pouvoir rechercher du texte entrant dans cette liste de mots. Lorsque Python le charge en mémoire, il indique 1,3 Go d’espace RAM utilisé. Une idée pourquoi c'est? 

alors disons que mon fichier Word ressemble à ceci ...

1,Word1
2,Word2
3,Word3

puis ajoutez 6,5 millions à cela J'ai ensuite parcouru ce fichier et créé un dictionnaire (python 2.6.1)

  def load_term_cache():
      """will load the term cache from our cached file instead of hitting mysql. If it didn't 
      preload into memory it would be 20+ million queries per process"""
      global cached_terms
      dumpfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt')
      f = open(dumpfile)
      cache = csv.reader(f)
      for term_id, term in cache:
          cached_terms[term] = term_id
      f.close()

Rien que ça fait exploser la mémoire. Je visualise le moniteur d'activité et il fixe la mémoire sur toute la quantité disponible jusqu'à environ 1,5 Go de RAM sur mon ordinateur portable, il commence tout juste à être échangé. Des idées sur la manière de stocker le plus efficacement possible les paires clé/valeur en mémoire avec python?

merci

UPDATE: J'ai essayé d'utiliser le module anydb et après 4,4 millions d'enregistrements, il meurt Le nombre à virgule flottante correspond aux secondes écoulées depuis que j'ai essayé de le charger.

56.95
3400018
60.12
3600019
63.27
3800020
66.43
4000021
69.59
4200022
72.75
4400023
83.42
4600024
168.61
4800025
338.57

vous pouvez voir qu'il fonctionnait très bien. 200 000 rangées toutes les quelques secondes insérées jusqu'à ce que je frappe un mur et que le temps double.

  import anydbm
  i=0
  mark=0
  starttime = time.time()
  dbfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms')
  db = anydbm.open(dbfile, 'c')
  #load from existing baseterm file
  termfile = os.path.join(os.getenv("MY_PATH"), 'datafiles', 'baseterms.txt.LARGE')
  for line in open(termfile):
    i += 1
    pieces = line.split(',')
    db[str(pieces[1])] = str(pieces[0])
    if i > mark:
      print i
      print round(time.time() - starttime, 2)
      mark = i + 200000
  db.close()
29
James

Beaucoup d'idées. Toutefois, si vous souhaitez une aide pratique, modifiez votre question pour afficher TOUS vos codes. Indiquez-nous également quel est le "it" qui indique la mémoire utilisée, ce qui est indiqué lorsque vous chargez un fichier ne contenant aucune entrée, et sur quelle plate-forme vous vous trouvez, ainsi que votre version de Python.

Vous dites que "la Parole peut être 1-5 mots". Quelle est la longueur moyenne du champ clé dans BYTES? Les identifiants sont-ils tous entiers? Si oui, quels sont les entiers min et max? Si non, quelle est la longueur moyenne si ID en octets? Pour permettre le contrôle croisé de tout ce qui précède, combien d’octets dans votre fichier de 6,5 millions de lignes?

En regardant votre code, un fichier d'une ligne Word1,1 créera un dict d['1'] = 'Word1' ... n'est-ce pas bassackwards?

Mise à jour 3: Autres questions: Comment le "mot" est-il codé? Êtes-vous sûr de ne pas avoir une charge d'espaces de fin sur l'un des deux champs?

Mise à jour 4 ... Vous avez demandé " comment stocker le plus efficacement possible les paires clé/valeur en mémoire avec python " et personne n'a encore répondu avec précision .

Vous avez un fichier de 168 Mo avec 6,5 millions de lignes. C'est 168 * 1.024 ** 2/6.5 = 27,1 octets par ligne. Désactivez 1 octet pour la virgule et 1 octet pour la nouvelle ligne (en supposant qu'il s'agisse d'une plate-forme * x) et il nous reste 25 octets par ligne. En supposant que "id" est destiné à être unique, et comme il semble être un entier, supposons que "id" a une longueur de 7 octets; cela nous laisse avec une taille moyenne de 18 octets pour le "mot". Cela correspond-il à vos attentes?

Nous voulons donc stocker une clé de 18 octets et une valeur de 7 octets dans une table de consultation en mémoire.

Supposons une plate-forme CPython 2.6 32 bits.

>>> K = sys.getsizeof('123456789012345678')
>>> V = sys.getsizeof('1234567')
>>> K, V
(42, 31)

Notez que sys.getsizeof(str_object) => 24 + len(str_object)

Les tuples ont été mentionnés par un répondant. Notez attentivement les points suivants:

>>> sys.getsizeof(())
28
>>> sys.getsizeof((1,))
32
>>> sys.getsizeof((1,2))
36
>>> sys.getsizeof((1,2,3))
40
>>> sys.getsizeof(("foo", "bar"))
36
>>> sys.getsizeof(("fooooooooooooooooooooooo", "bar"))
36
>>>

Conclusion: sys.getsizeof(Tuple_object) => 28 + 4 * len(Tuple_object) ... ... il ne permet qu'un pointeur sur chaque élément, il ne permet pas la taille des éléments.  

Une analyse similaire des listes montre que sys.getsizeof(list_object) => 36 + 4 * len(list_object) ... encore une fois, il est nécessaire d’ajouter la taille des éléments. Il y a une autre considération à prendre en compte: CPython sur-attribue les listes de manière à ne pas avoir à appeler realloc () du système à chaque appel de list.append (). Pour une taille suffisamment grande (environ 6,5 millions!), La surallocation est de 12,5% - voir la source (Objects/listobject.c). Cette surallocation ne se fait pas avec des n-uplets (leur taille ne change pas).

Voici les coûts de différentes alternatives à dicter pour une table de correspondance basée sur la mémoire:

Liste des tuples:

Chaque tuple prendra 36 octets pour le 2-tuple lui-même, plus K et V pour le contenu. Donc, N d’entre eux prendront N * (36 + K + V); alors vous avez besoin d’une liste pour les conserver, nous avons donc besoin de 36 + 1.125 * 4 * N pour cela.

Total pour la liste de tuples: 36 + N * (40,5 + K + v)

C'est 26 + 113,5 * N ( environ 709 Mo quand est 6,5 millions)

Deux listes parallèles:

(36 + 1,125 * 4 * N + K * N) + (36 + 1,125 * 4 * N + V * N) I.e. 72 + N * (9 + K + V)

Notez que la différence entre 40,5 * N et 9 * N est d’environ 200 Mo lorsque N est de 6,5 millions.

Valeur stockée sous la forme int non str:

Mais ce n'est pas tout. Si les identifiants sont en fait des entiers, nous pouvons les stocker tels quels.

>>> sys.getsizeof(1234567)
12

C'est 12 octets au lieu de 31 octets pour chaque objet de valeur. Cette différence de 19 * N représente une économie supplémentaire d’environ 118 Mo lorsque N est de 6,5 millions.

Utilisez array.array ('l') au lieu de list pour la valeur (entière):

Nous pouvons stocker ces entiers à 7 chiffres dans un tableau.array ('l'). Pas d'objets int, et pas de pointeurs vers eux - juste une valeur entière signée sur 4 octets. Bonus: les tableaux ne sont sur-localisés que par 6,25% (pour les gros N). Il s’agit donc de 1,0625 * 4 * N au lieu du précédent (1.125 * 4 + 12) * N, soit une économie supplémentaire de 12,25 * N, soit 76 Mo.

Nous en sommes donc à 709 - 200 - 118 - 76 = environ 315 Mo .

N.B. Erreurs et omissions exceptées - il est 0127 dans mon TZ :-( 

35
John Machin

Jetez un coup d'oeil (Python 2.6, version 32 bits) ...:

>>> sys.getsizeof('Word,1')
30
>>> sys.getsizeof(('Word', '1'))
36
>>> sys.getsizeof(dict(Word='1'))
140

La chaîne (prenant 6 octets sur le disque, clairement) génère une surcharge de 24 octets (peu importe sa longueur, ajoutez 24 à sa longueur pour déterminer la quantité de mémoire nécessaire). Lorsque vous le divisez en un tuple, c'est un peu plus. Mais la dict est ce qui fait exploser les choses: même un dict vide prend 140 octets, ce qui est purement superflu de maintenir une prise de vue extrêmement rapide à base de hachage. Pour être rapide, une table de hachage doit avoir une densité faible - et Python garantit que dict a toujours une densité faible (en occupant beaucoup de mémoire supplémentaire pour elle).

Le moyen le plus efficace en mémoire de stocker des paires clé/valeur est une liste de nuplets, mais la recherche sera bien sûr very slow (même si vous triez la liste et utilisez bisect pour la recherche, elle est toujours active. être extrêmement plus lent qu'un dictée).

Pensez à utiliser shelve à la place - qui utilisera peu de mémoire (les données résidant sur le disque) tout en offrant des performances de recherche assez spiffantes (pas aussi rapide qu'un dict en mémoire, bien sûr, mais pour une grande quantité de données). données, il sera beaucoup plus rapide que la recherche sur une liste de n-uplets, même triée, ne peut jamais être! -).

20
Alex Martelli

convertissez vos données en dbm (importez anydbm, ou utilisez berkerley db en important bsddb ...), puis utilisez l'API dbm pour y accéder.

la raison de l'explosion est que python a des méta-informations supplémentaires pour tous les objets et que dict a besoin de construire une table de hachage (qui nécessiterait plus de mémoire). vous venez de créer autant d'objets (6,5 M) que les métadonnées deviennent trop énormes.

import bsddb
a = bsddb.btopen('a.bdb') # you can also try bsddb.hashopen
for x in xrange(10500) :
  a['Word%d' %x] = '%d' %x
a.close()

Ce code ne prend que 1 seconde, alors je pense que la vitesse est bonne (puisque vous avez dit 10500 lignes par seconde). Btopen crée un fichier db de 499 712 octets de long et hashopen en crée 319 488.

Avec une entrée xrange de 6,5 M et l’utilisation de btopen, j’ai obtenu 417 080 Ko de taille de fichier de sortie et environ 1 ou 2 minutes pour terminer l’insertion. Je pense donc que cela vous convient parfaitement.

8
Francis

J'ai le même problème bien que je sois plus tard. Les autres ont bien répondu à cette question. Et j'offre un facile à utiliser(Peut-être pas si facile :-)) et alternative plutôt efficace, c'est pandas.DataFrame. Il fonctionne bien en mémoire lors de la sauvegarde de données volumineuses.

0
gzc