A tuple
prend moins d'espace mémoire en Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
alors que list
s prend plus d'espace mémoire:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Que se passe-t-il en interne sur la gestion de la mémoire Python?
A tuple
prend moins d'espace mémoire en Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
alors que list
s prend plus d'espace mémoire:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Que se passe-t-il en interne sur la gestion de la mémoire Python?
Réponses:
Je suppose que vous utilisez CPython et avec 64 bits (j'ai obtenu les mêmes résultats sur mon CPython 2.7 64 bits). Il peut y avoir des différences dans d'autres implémentations Python ou si vous avez un Python 32 bits.
Indépendamment de l'implémentation,
list
s sont de taille variable tandis quetuple
s sont de taille fixe.Pour que
tuple
s puisse stocker les éléments directement à l'intérieur de la structure, les listes par contre ont besoin d'une couche d'indirection (elle stocke un pointeur vers les éléments). Cette couche d'indirection est un pointeur, sur les systèmes 64 bits soit 64 bits, donc 8 octets.Mais il y a une autre chose à
list
faire: ils surallouent. Dans le cas contrairelist.append
serait uneO(n)
opération toujours - pour le rendre amortiO(1)
(beaucoup plus rapide !!!) il sur-Alloue. Mais maintenant, il doit garder une trace de la taille allouée et de la taille remplie (iltuple
suffit de stocker une taille, car la taille allouée et la taille remplie sont toujours identiques). Cela signifie que chaque liste doit stocker une autre "taille" qui sur les systèmes 64 bits est un entier de 64 bits, encore une fois 8 octets.Donc, il
list
faut au moins 16 octets de mémoire de plus quetuple
s. Pourquoi ai-je dit «au moins»? En raison de la surallocation. La surallocation signifie qu'elle alloue plus d'espace que nécessaire. Cependant, le montant de la surallocation dépend de la «façon» dont vous créez la liste et de l'historique des ajouts / suppressions:Images
J'ai décidé de créer quelques images pour accompagner l'explication ci-dessus. Peut-être que ceux-ci sont utiles
Voici comment il (schématiquement) est stocké en mémoire dans votre exemple. J'ai mis en évidence les différences avec les cycles rouges (à main levée):
Ce n'est en fait qu'une approximation car les
int
objets sont également des objets Python et CPython réutilise même de petits entiers, donc une représentation probablement plus précise (bien que pas aussi lisible) des objets en mémoire serait:Liens utiles:
tuple
struct dans le référentiel CPython pour Python 2.7list
struct dans le référentiel CPython pour Python 2.7int
struct dans le référentiel CPython pour Python 2.7Notez que
__sizeof__
cela ne renvoie pas vraiment la taille "correcte"! Il ne renvoie que la taille des valeurs stockées. Cependant lorsque vous utilisezsys.getsizeof
le résultat est différent:Il y a 24 octets "supplémentaires". Ce sont réels , c'est la surcharge du ramasse-miettes qui n'est pas prise en compte dans la
__sizeof__
méthode. C'est parce que vous n'êtes généralement pas censé utiliser directement les méthodes magiques - utilisez les fonctions qui savent comment les gérer, dans ce cas:sys.getsizeof
(qui ajoute en fait la surcharge GC à la valeur renvoyée__sizeof__
).la source
list
allocation de mémoire stackoverflow.com/questions/40018398/…list()
ou de la compréhension d'une liste.Je vais plonger plus profondément dans la base de code CPython afin que nous puissions voir comment les tailles sont réellement calculées. Dans votre exemple spécifique , aucune sur-allocation n'a été effectuée, je ne vais donc pas en parler .
Je vais utiliser des valeurs 64 bits ici, comme vous l'êtes.
La taille de
list
s est calculée à partir de la fonction suivantelist_sizeof
:Voici
Py_TYPE(self)
une macro qui saisit leob_type
deself
(retourPyList_Type
) tandis_PyObject_SIZE
qu'une autre macro qui saisittp_basicsize
de ce type.tp_basicsize
est calculé commesizeof(PyListObject)
où sePyListObject
trouve la structure d'instance.La
PyListObject
structure comporte trois champs:ceux-ci ont des commentaires (que j'ai coupés) expliquant ce qu'ils sont, suivez le lien ci-dessus pour les lire.
PyObject_VAR_HEAD
se développe en trois champs de 8 octets (ob_refcount
,ob_type
etob_size
) donc une24
contribution d'octets.Donc pour l'instant
res
c'est:ou:
Si l'instance de liste a des éléments alloués. la deuxième partie calcule leur contribution.
self->allocated
, comme son nom l'indique, contient le nombre d'éléments alloués.Sans aucun élément, la taille des listes est calculée comme suit:
c'est-à-dire la taille de la structure d'instance.
tuple
les objets ne définissent pas unetuple_sizeof
fonction. Au lieu de cela, ils utilisentobject_sizeof
pour calculer leur taille:Ceci, comme pour
list
s, saisit letp_basicsize
et, si l'objet a un non-zérotp_itemsize
(ce qui signifie qu'il a des instances de longueur variable), il multiplie le nombre d'éléments dans le tuple (qu'il obtient viaPy_SIZE
) avectp_itemsize
.tp_basicsize
utilise à nouveausizeof(PyTupleObject)
où laPyTupleObject
structure contient :Donc, sans aucun élément (c'est-à-dire sans
Py_SIZE
retour0
), la taille des tuples vides est égale àsizeof(PyTupleObject)
:hein? Eh bien, voici une bizarrerie pour laquelle je n'ai pas trouvé d'explication, le
tp_basicsize
detuple
s est en fait calculé comme suit:pourquoi un
8
octet supplémentaire est supprimétp_basicsize
est quelque chose que je n'ai pas pu découvrir. (Voir le commentaire de MSeifert pour une explication possible)Mais, c'est fondamentalement la différence dans votre exemple spécifique .
list
s conservent également un certain nombre d'éléments alloués, ce qui permet de déterminer le moment de surallouer à nouveau.Désormais, lorsque des éléments supplémentaires sont ajoutés, les listes effectuent effectivement cette surallocation afin d'obtenir des appendices O (1). Cela se traduit par des tailles plus grandes car MSeifert couvre bien sa réponse.
la source
ob_item[1]
est principalement un espace réservé (il est donc logique qu'il soit soustrait de la taille de base). Letuple
est attribué en utilisantPyObject_NewVar
. Je n'ai pas compris les détails, donc c'est juste une suppositionLa réponse de MSeifert le couvre largement; pour faire simple, vous pouvez penser à:
tuple
est immuable. Une fois défini, vous ne pouvez pas le modifier. Vous savez donc à l'avance la quantité de mémoire à allouer à cet objet.list
est mutable. Vous pouvez y ajouter ou supprimer des éléments. Il doit en connaître la taille (pour implication interne). Il se redimensionne selon les besoins.Il n'y a pas de repas gratuits - ces capacités ont un coût. D'où la surcharge de mémoire pour les listes.
la source
La taille du tuple est préfixée, ce qui signifie qu'à l'initialisation du tuple, l'interpréteur alloue suffisamment d'espace pour les données contenues, et c'est la fin, ce qui le rend immuable (ne peut pas être modifié), alors qu'une liste est un objet mutable impliquant donc dynamique allocation de mémoire, donc pour éviter d'allouer de l'espace à chaque fois que vous ajoutez ou modifiez la liste (allouez suffisamment d'espace pour contenir les données modifiées et y copier les données), il alloue de l'espace supplémentaire pour les ajouts futurs, les modifications, ... résume.
la source