A tuple
ocupa menos espaço de memória em Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
enquanto list
s ocupa mais espaço na memória:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
O que acontece internamente no gerenciamento de memória Python?
A tuple
ocupa menos espaço de memória em Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
enquanto list
s ocupa mais espaço na memória:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
O que acontece internamente no gerenciamento de memória Python?
Respostas:
Presumo que você esteja usando CPython e com 64 bits (obtive os mesmos resultados no meu CPython 2.7 de 64 bits). Pode haver diferenças em outras implementações Python ou se você tiver um Python de 32 bits.
Independentemente da implementação,
list
s são de tamanho variável enquantotuple
s são de tamanho fixo.Assim,
tuple
s podem armazenar os elementos diretamente dentro da estrutura; por outro lado, as listas precisam de uma camada de indireção (ela armazena um ponteiro para os elementos). Essa camada de indireção é um ponteiro, em sistemas de 64 bits que é 64 bits, portanto, 8 bytes.Mas há outra coisa que eles
list
fazem: eles alocam em excesso. Caso contrário,list.append
seria umaO(n)
operação sempre - para torná-la amortizadaO(1)
(muito mais rápido !!!) ela superaloca. Mas agora ele precisa manter o controle do tamanho alocado e do tamanho preenchido (os tamanhostuple
só precisam armazenar um tamanho, porque o tamanho alocado e preenchido são sempre idênticos). Isso significa que cada lista deve armazenar outro "tamanho" que em sistemas de 64 bits é um número inteiro de 64 bits, novamente 8 bytes.Portanto, os
list
s precisam de pelo menos 16 bytes a mais de memória do que ostuple
s. Por que eu disse "pelo menos"? Por causa da superalocação. Superalocação significa que aloca mais espaço do que o necessário. No entanto, a quantidade de superalocação depende de "como" você cria a lista e do histórico de acréscimo / exclusão:Imagens
Resolvi criar algumas imagens para acompanhar a explicação acima. Talvez sejam úteis
É assim que (esquematicamente) é armazenado na memória em seu exemplo. Eu destaquei as diferenças com os ciclos vermelhos (mão livre):
Na verdade, isso é apenas uma aproximação porque os
int
objetos também são objetos Python e o CPython até reutiliza pequenos inteiros, então uma representação provavelmente mais precisa (embora não tão legível) dos objetos na memória seria:Links Úteis:
tuple
struct no repositório CPython para Python 2.7list
struct no repositório CPython para Python 2.7int
struct no repositório CPython para Python 2.7Observe que
__sizeof__
realmente não retorna o tamanho "correto"! Ele apenas retorna o tamanho dos valores armazenados. No entanto, quando você usa,sys.getsizeof
o resultado é diferente:Existem 24 bytes "extras". Eles são reais , essa é a sobrecarga do coletor de lixo que não é considerada no
__sizeof__
método. Isso porque geralmente você não deve usar métodos mágicos diretamente - use as funções que sabem como tratá-los, neste caso:sys.getsizeof
(que na verdade adiciona a sobrecarga de GC ao valor retornado de__sizeof__
).fonte
list
alocação de memória stackoverflow.com/questions/40018398/…list()
ou uma compreensão de lista.Vou dar um mergulho mais profundo na base de código CPython para que possamos ver como os tamanhos são realmente calculados. Em seu exemplo específico , nenhuma superalocação foi realizada, então não tocarei nisso .
Vou usar valores de 64 bits aqui, como você.
O tamanho de
list
s é calculado a partir da seguinte funçãolist_sizeof
:Aqui
Py_TYPE(self)
está uma macro que capturaob_type
deself
(retornandoPyList_Type
) enquanto_PyObject_SIZE
outra macro capturatp_basicsize
desse tipo.tp_basicsize
é calculado comosizeof(PyListObject)
ondePyListObject
está a estrutura da instância.A
PyListObject
estrutura possui três campos:estes têm comentários (que cortei) explicando o que são, siga o link acima para lê-los.
PyObject_VAR_HEAD
expande-se em três campos de 8 byte (ob_refcount
,ob_type
eob_size
) para uma24
contribuição byte.Então, por enquanto
res
é:ou:
Se a instância da lista tiver elementos que estão alocados. a segunda parte calcula sua contribuição.
self->allocated
, como o nome indica, contém o número de elementos alocados.Sem quaisquer elementos, o tamanho das listas é calculado para ser:
ou seja, o tamanho da estrutura da instância.
tuple
objetos não definem umatuple_sizeof
função. Em vez disso, eles usamobject_sizeof
para calcular seu tamanho:Isso, como para
list
s, agarra otp_basicsize
e, se o objeto tiver um diferente de zerotp_itemsize
(o que significa que ele tem instâncias de comprimento variável), ele multiplica o número de itens na tupla (pelos quais ele obtém viaPy_SIZE
)tp_itemsize
.tp_basicsize
novamente usasizeof(PyTupleObject)
onde aPyTupleObject
estrutura contém :Portanto, sem nenhum elemento (ou seja,
Py_SIZE
retornos0
), o tamanho das tuplas vazias é igual asizeof(PyTupleObject)
:Hã? Bem, aqui está uma esquisitice para a qual não encontrei uma explicação, o
tp_basicsize
detuple
s é calculado da seguinte maneira:por que um
8
byte adicional é removidotp_basicsize
é algo que não consegui descobrir. (Veja o comentário de MSeifert para uma possível explicação)Mas, essa é basicamente a diferença em seu exemplo específico .
list
s também mantêm vários elementos alocados que ajudam a determinar quando alocar em excesso novamente.Agora, quando elementos adicionais são adicionados, as listas de fato realizam essa superalocação para obter anexos O (1). Isso resulta em tamanhos maiores, pois o de MSeifert cobre bem em sua resposta.
fonte
ob_item[1]
é principalmente um espaço reservado (então faz sentido que seja subtraído do tamanho básico). Otuple
é alocado usandoPyObject_NewVar
. Eu não descobri os detalhes, então isso é apenas um palpite ...A resposta da MSeifert cobre isso amplamente; para mantê-lo simples, você pode pensar em:
tuple
é imutável. Depois de definido, você não pode alterá-lo. Portanto, você sabe com antecedência quanta memória precisa alocar para esse objeto.list
é mutável. Você pode adicionar ou remover itens de ou para ele. Tem que saber o tamanho dele (para impl. Interno). Ele é redimensionado conforme necessário.Não há refeições gratuitas - esses recursos têm um custo. Daí a sobrecarga de memória para listas.
fonte
O tamanho da tupla é prefixado, o que significa que na inicialização da tupla o interpretador aloca espaço suficiente para os dados contidos, e ponto final, dando-lhe imutável (não pode ser modificado), enquanto uma lista é um objeto mutável, portanto, implicando dinâmico alocação de memória, para evitar a alocação de espaço cada vez que você anexar ou modificar a lista (alocar espaço suficiente para conter os dados alterados e copiar os dados para eles), ele aloca espaço adicional para anexos futuros, modificações, ... resume tudo.
fonte