Criei duas listas l1
e l2
, cada uma com um método de criação diferente:
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))
Mas a saída me surpreendeu:
Size of l1 = 144
Size of l2 = 192
A lista criada com uma compreensão da lista tem um tamanho maior na memória, mas as duas listas são idênticas no Python.
Por que é que? Isso é alguma coisa interna do CPython, ou alguma outra explicação?
python
list
memory-management
python-internals
Andrej Kesely
fonte
fonte
144 == sys.getsizeof([]) + 8*10)
onde 8 é do tamanho de um ponteiro.10
para11
, a[None] * 11
lista terá tamanho152
, mas a compreensão da lista ainda terá tamanho192
. A pergunta anteriormente vinculada não é uma duplicata exata, mas é relevante para entender por que isso acontece.Respostas:
Quando você escreve
[None] * 10
, o Python sabe que precisará de uma lista de exatamente 10 objetos, portanto, aloca exatamente isso.Quando você usa uma compreensão de lista, o Python não sabe quanto será necessário. Por isso, aumenta gradualmente a lista à medida que os elementos são adicionados. Para cada realocação, ele aloca mais espaço do que o necessário imediatamente, para que não precise realocar para cada elemento. A lista resultante provavelmente será um pouco maior que o necessário.
Você pode ver esse comportamento ao comparar listas criadas com tamanhos semelhantes:
Você pode ver que o primeiro método aloca exatamente o necessário, enquanto o segundo cresce periodicamente. Neste exemplo, ele aloca o suficiente para 16 elementos e precisou ser realocado ao atingir o 17º.
fonte
*
quando sei o tamanho à frente.[x] * n
com imutávelx
na sua lista. A lista resultante conterá referências ao objeto idêntico.Conforme observado nesta pergunta, a compreensão da lista usa
list.append
sob o capô, portanto, ele chamará o método de redimensionamento de lista, que atribui globalmente.Para demonstrar isso, você pode realmente usar o
dis
desmontador:Observe o
LIST_APPEND
código de operação na desmontagem do<listcomp>
objeto de código. Dos documentos :Agora, para a operação de repetição de lista, temos uma dica sobre o que está acontecendo se considerarmos:
Portanto, parece poder alocar exatamente o tamanho. Observando o código fonte , vemos que é exatamente isso que acontece:
Ou seja, aqui:
size = Py_SIZE(a) * n;
. O restante das funções simplesmente preenche a matriz.fonte
.extend()
.list.append
é uma operação de tempo constante amortizada porque, quando uma lista é redimensionada, ela é atribuída globalmente. Portanto, nem toda operação de acréscimo resulta em uma matriz recém-alocada. Em qualquer caso, a pergunta que eu ligado a mostra-lo no código-fonte que, na verdade, compreensões lista fazer usolist.append
,. Volto para o meu laptop em um momento e eu posso mostrar-lhe o bytecode desmontado para uma compreensão da lista e do correspondenteLIST_APPEND
código de operaçãoNenhum é um bloco de memória, mas não é um tamanho pré-especificado. Além disso, há algum espaçamento extra em uma matriz entre os elementos da matriz. Você pode ver isso executando:
O que não totaliza o tamanho de l2, mas é menor.
E isso é muito maior que um décimo do tamanho de
l1
.Seus números devem variar de acordo com os detalhes do seu sistema operacional e os detalhes do uso atual da memória no seu sistema operacional. O tamanho de [None] nunca pode ser maior que a memória adjacente disponível onde a variável está configurada para ser armazenada e a variável pode precisar ser movida se posteriormente for alocada dinamicamente para ser maior.
fonte
None
não é realmente armazenado na matriz subjacente, a única coisa que é armazenada é umPyObject
ponteiro (8 bytes). Todos os objetos Python são alocados no heap.None
é um singleton, portanto, ter uma lista com muitos nomes simplesmente criará uma matriz de ponteiros PyObject para o mesmoNone
objeto na pilha (e não usará memória adicional no processo por adicionalNone
). Não sei ao certo o que você quer dizer com "Nenhum não tem um tamanho pré-especificado", mas isso não parece correto. Finalmente, seu loop comgetsizeof
cada elemento não está demonstrando o que você parece pensar que está demonstrando.gestsizeof
em cadaele
del2
é enganosa, porquegetsizeof(l2)
não leva em conta o tamanho dos elementos dentro do recipiente .l1 = [None]; l2 = [None]*100; l3 = [l2]
-oprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. você vai ter um resultado como:72 864 72
. Isto é, respectivamente,64 + 1*8
,64 + 100*8
, e64 + 1*8
, novamente, assumindo um sistema de 64 bits com tamanho ponteiro de 8 bytes.sys.getsizeof
* não é responsável pelo tamanho dos itens no contêiner. Na documentação : "Somente o consumo de memória diretamente atribuído ao objeto é contabilizado, não o consumo de memória dos objetos aos quais ele se refere ... Consulte receita recursiva de sizeof para obter um exemplo de uso de getsizeof () recursivamente para encontrar o tamanho de contêineres e todo o seu conteúdo ".