J'ai créé deux listes l1
et l2
, mais chacune avec une méthode de création différente:
import sys
l1 = [None] * 10
l2 = [None for _ in range(10)]
print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))
Mais la sortie m'a surpris:
Size of l1 = 144
Size of l2 = 192
La liste créée avec une compréhension de liste est une plus grande taille en mémoire, mais les deux listes sont identiques en Python sinon.
Pourquoi donc? Est-ce une chose interne à CPython, ou une autre explication?
python
list
memory-management
python-internals
Andrej Kesely
la source
la source
144 == sys.getsizeof([]) + 8*10)
où 8 est la taille d'un pointeur.10
à11
, la[None] * 11
liste a une taille152
, mais la compréhension de la liste a toujours une taille192
. La question précédemment liée n'est pas une copie exacte, mais elle est pertinente pour comprendre pourquoi cela se produit.Réponses:
Lorsque vous écrivez
[None] * 10
, Python sait qu'il aura besoin d'une liste d'exactement 10 objets, donc il alloue exactement cela.Lorsque vous utilisez une compréhension de liste, Python ne sait pas de combien il aura besoin. Ainsi, la liste augmente progressivement au fur et à mesure que des éléments sont ajoutés. Pour chaque réallocation, il alloue plus d'espace que ce qui est immédiatement nécessaire, de sorte qu'il n'a pas à réallouer pour chaque élément. La liste résultante sera probablement un peu plus longue que nécessaire.
Vous pouvez voir ce comportement lorsque vous comparez des listes créées avec des tailles similaires:
Vous pouvez voir que la première méthode alloue juste ce qui est nécessaire, tandis que la seconde se développe périodiquement. Dans cet exemple, il alloue assez pour 16 éléments, et a dû réallouer en atteignant le 17e.
la source
*
quand je connais la taille devant.[x] * n
avec immuablex
dans votre liste. La liste résultante contiendra des références à l'objet identique.Comme indiqué dans cette question, la compréhension de la liste utilise
list.append
sous le capot, elle appellera donc la méthode list-resize, qui surutilisera.Pour vous le démontrer, vous pouvez réellement utiliser le
dis
dissasembler:Notez l'
LIST_APPEND
opcode dans le désassemblage de l'<listcomp>
objet code. À partir de la documentation :Maintenant, pour l'opération de répétition de liste, nous avons un indice sur ce qui se passe si nous considérons:
Donc, il semble être en mesure d' allouer exactement la taille. En regardant le code source , nous voyons que c'est exactement ce qui se passe:
A savoir, ici:
size = Py_SIZE(a) * n;
. Le reste des fonctions remplit simplement le tableau.la source
.extend()
.list.append
est une opération à temps constant amorti car lorsqu'une liste se redimensionne, elle surutilisera. Toutes les opérations d'ajout ne donnent donc pas lieu à un nouveau tableau alloué. En tout cas , la question que je vous montre lié à dans le code source qui en fait, compréhensions liste font usagelist.append
,. Je serai de retour à mon ordinateur portable dans un instant et je peux vous montrer le bytecode démonté pour une compréhension de la liste et l'LIST_APPEND
opcode correspondantAucun n'est un bloc de mémoire, mais ce n'est pas une taille pré-spécifiée. En plus de cela, il y a un espacement supplémentaire dans un tableau entre les éléments du tableau. Vous pouvez le voir vous-même en exécutant:
Ce qui ne totalise pas la taille de l2, mais est plutôt moindre.
Et c'est beaucoup plus d'un dixième de la taille de
l1
.Vos chiffres doivent varier en fonction des détails de votre système d'exploitation et des détails de l'utilisation actuelle de la mémoire dans votre système d'exploitation. La taille de [Aucun] ne peut jamais être plus grande que la mémoire adjacente disponible où la variable est définie pour être stockée, et la variable peut devoir être déplacée si elle est allouée dynamiquement plus tard pour être plus grande.
la source
None
n'est pas réellement stocké dans le tableau sous-jacent, la seule chose qui est stockée est unPyObject
pointeur (8 octets). Tous les objets Python sont alloués sur le tas.None
est un singleton, donc avoir une liste avec de nombreux nones est simplement créer un tableau de pointeurs PyObject vers le mêmeNone
objet sur le tas (et ne pas utiliser de mémoire supplémentaire dans le processus par supplémentaireNone
). Je ne suis pas sûr de ce que vous entendez par "Aucun n'a une taille pré-spécifiée", mais cela ne semble pas correct. Enfin, votre boucle avecgetsizeof
chaque élément ne montre pas ce que vous semblez penser qu'elle démontre.gestsizeof
sur chacunele
del2
est trompeuse cargetsizeof(l2)
ne prend pas en compte la taille des éléments à l'intérieur du conteneur .l1 = [None]; l2 = [None]*100; l3 = [l2]
alorsprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. vous obtiendrez un résultat comme:72 864 72
. C'est, respectivement,64 + 1*8
,64 + 100*8
et64 + 1*8
, encore une fois, en supposant un système 64 bits avec la taille du pointeur de 8 octets.sys.getsizeof
* ne tient pas compte de la taille des articles dans le conteneur. D'après la documentation : "Seule la consommation de mémoire directement attribuée à l'objet est prise en compte, pas la consommation de mémoire des objets auxquels il se réfère ... Voir la recette récursive sizeof pour un exemple d'utilisation récursive de getsizeof () pour trouver la taille des conteneurs et tout leur contenu. "