2009-02-21 12 views
3

GAE a diverses limitations, dont l'une est la taille du plus grand bloc allouable de mémoire s'élevant à 1 Mo (maintenant 10 fois plus, mais cela ne change pas la question). La limitation signifie que l'on ne peut mettre plus d'un certain nombre d'éléments dans list() car CPython essayera d'allouer un bloc mémoire contigu pour les pointeurs d'éléments. Avoir d'énormes listes() peut être considéré comme une mauvaise pratique de programmation, mais même si aucune structure énorme n'est créée dans le programme lui-même, CPython en conserve quelques-unes dans les coulisses.Structures internes de CPython

Il semble que CPython maintient une liste globale unique d'objets ou quelque chose. C'est à dire. application qui a beaucoup de petits objets ont tendance à allouer de plus en plus gros blocs de mémoire.

La première idée était gc, et la désactiver change un peu le comportement de l'application mais certaines structures sont maintenues.

Une application courte simple que l'expérience de la question est:

a = b = [] 
number_of_lists = 8000000 
for i in xrange(number_of_lists): 
    b.append([]) 
    b = b[0] 

Quelqu'un peut-il me éclairer comment empêcher CPython d'allouer d'énormes structures internes en ayant beaucoup d'objets dans l'application?

+0

Quelle est la question ** réelle **. Pourquoi l'épuisement de la mémoire importe-t-il? Quel problème particulier avez-vous qui semble exiger des blocs de mémoire géants? Vous pouvez probablement résoudre ce problème pour ne pas allouer de la mémoire en premier lieu. Quel est le problème ** réel ** que vous essayez de résoudre? –

Répondre

8

Sur un système 32 bits, chacune des 800 000 listes que vous créez allouera 20 octets pour l'objet liste lui-même, plus 16 octets pour un vecteur d'éléments de liste. Donc, vous essayez d'allouer au moins (20 + 16) * 8000000 = 20168000000 octets, environ 20 Go. Et dans le meilleur des cas, si le système malloc n'alloue que la quantité de mémoire demandée.

I a calculé la taille de l'objet de la liste comme suit:

  • 2 pointeurs dans la même structure de PyListObject (voir listobject.h)
  • 1 pointeur et une Py_ssize_t pour la partie PyObject_HEAD de l'objet de liste (voir object.h)
  • une Py_ssize_t pour la PyObject_VAR_HEAD (également en object.h)

Le vecteur des éléments de liste est légèrement surutilisé pour éviter d'avoir à le redimensionner à chaque ajout - voir list_resize dans listobject.c. Les tailles sont 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... Ainsi, vos listes d'un élément alloueront de la place pour 4 éléments.

Votre structure de données est un exemple un peu pathologique, en payant le prix d'un objet de liste de taille variable sans l'utiliser - toutes vos listes n'ont qu'un seul élément. Vous pouvez éviter la superposition de 12 octets en utilisant des tuples au lieu de listes, mais pour réduire davantage la consommation de mémoire, vous devrez utiliser une structure de données différente qui utilise moins d'objets. Il est difficile d'être plus précis, car je ne sais pas ce que vous essayez d'accomplir.

0

Je suis un peu confus quant à ce que vous demandez. Dans cet exemple de code, rien ne doit être collecté, car vous ne supprimez jamais les références. Vous détenez une référence à la liste de premier niveau dans un et vous ajoutez des listes imbriquées (contenues dans b à chaque itération) à l'intérieur de cela. Si vous supprimez le 'a =', puis vous avez des objets non référencés.

Éditer: En réponse à la première partie, oui, Python contient une liste d'objets pour savoir ce qu'il faut éliminer. Est-ce toute la question? Sinon, commentez/modifiez votre question et je ferai de mon mieux pour aider à combler les lacunes.

+0

J'ai mis en contexte certaines questions pour clarification. Est ce que ça aide? – myroslav

0

Qu'est-ce que vous essayez d'accomplir avec les

a = b = [] 

et

b = b[0] 

déclarations? Il est certainement étrange de voir des instructions de ce type en Python, car elles ne font pas ce que vous pourriez espérer naïvement: dans cet exemple, a et b sont deux noms pour la liste (pensez aux pointeurs en C). Si vous faites beaucoup de manipulations comme ça, il est facile de confondre le garbage collector (et vous-même!) Parce que vous avez beaucoup de références étranges qui n'ont pas été correctement effacées.

Il est difficile de diagnostiquer ce qui ne va pas avec ce code sans savoir pourquoi vous voulez faire ce qu'il semble faire. Bien sûr, cela expose un peu l'étrangeté de l'interprète ... mais je suppose que vous abordez votre problème de façon étrange, et une approche plus Pythonique pourrait donner de meilleurs résultats.

+0

Je pense que c'était simplement un exemple donné pour poser des questions sur une structure GC sous-jacente, mais je ne pouvais pas m'en sortir non plus. –

+0

Oui, c'est juste un exemple * bizarre *. a = b = [] est assez facile à démêler (deux références à une liste qui commence vide), mais b = b [0] donne mon esprit en essayant de penser à l'intérieur de CPython. Peut-être que je devrais juste aller dormir déjà .... – kquinn

+0

Le programme d'exemple est quelque chose de plus simple pour engendrer d'énormes quantités d'objets liés qui ne peuvent pas être collectés. Après que l'exécution du programme pointe vers le plus haut contenant list(), chaque liste contient un seul élément qui est une autre liste contenant un autre élément ... – myroslav

0

Pour que vous en soyez conscient, Python a son propre allocateur. Vous pouvez le désactiver en utilisant --without-pyalloc pendant l'étape de configuration. Cependant, la plus grande arène est de 256 Ko, ce qui ne devrait pas poser de problème. Vous pouvez également compiler Python avec le débogage activé, en utilisant --with-pydebug. Cela vous donnera plus d'informations sur l'utilisation de la mémoire.

Je soupçonne votre intuition et je suis sûr que le diagnostic d'oefe est correct. Une liste utilise de la mémoire contiguë, donc si votre liste devient trop grande pour un système, alors vous n'avez pas de chance. Si vous êtes vraiment aventureux, vous pouvez réimplémenter PyList pour utiliser plusieurs blocs, mais cela demandera beaucoup de travail puisque plusieurs bits de Python attendent des données contiguës.