Eu tenho algumas perguntas relacionadas ao uso de memória no exemplo a seguir.
Se eu correr no intérprete,
foo = ['bar' for _ in xrange(10000000)]
a memória real usada na minha máquina sobe
80.9mb
. Eu então,del foo
memória real diminui, mas apenas para
30.4mb
. O intérprete usa a4.4mb
linha de base. Qual é a vantagem de não liberar26mb
memória para o sistema operacional? É porque o Python está "planejando com antecedência", pensando que você pode usar tanta memória novamente?Por que ele é lançado
50.5mb
em particular - qual é a quantidade lançada com base?Existe uma maneira de forçar o Python a liberar toda a memória usada (se você souber que não usará tanta memória novamente)?
NOTA
Esta pergunta é diferente de Como posso liberar explicitamente memória no Python?
porque essa pergunta lida principalmente com o aumento do uso de memória da linha de base, mesmo depois que o intérprete libera objetos por meio da coleta de lixo (com gc.collect
ou sem uso).
fonte
gc.collect
.Respostas:
A memória alocada no heap pode estar sujeita a marcas d'água alta. Isso é complicado pelas otimizações internas do Python para alocar objetos pequenos (
PyObject_Malloc
) em conjuntos de 4 KiB, classificados para tamanhos de alocação em múltiplos de 8 bytes - até 256 bytes (512 bytes em 3.3). Os próprios pools estão em arenas de 256 KiB; portanto, se apenas um bloco em um pool for usado, toda a arena de 256 KiB não será liberada. No Python 3.3, o alocador de objetos pequenos foi alterado para o uso de mapas de memória anônima em vez do heap, portanto, ele deveria ter um desempenho melhor ao liberar memória.Além disso, os tipos internos mantêm freelists de objetos alocados anteriormente que podem ou não usar o alocador de objetos pequenos. O
int
tipo mantém um freelist com sua própria memória alocada e limpá-lo requer que seja chamadoPyInt_ClearFreeList()
. Isso pode ser chamado indiretamente, fazendo um totalgc.collect
.Tente assim e me diga o que você recebe. Aqui está o link para psutil.Process.memory_info .
Resultado:
Editar:
Eu mudei para medir em relação ao tamanho da VM do processo para eliminar os efeitos de outros processos no sistema.
O tempo de execução C (por exemplo, glibc, msvcrt) reduz a pilha quando o espaço livre contíguo na parte superior atinge um limite constante, dinâmico ou configurável. Com glibc, você pode ajustar isso com
mallopt
(M_TRIM_THRESHOLD). Diante disso, não é de surpreender que o heap diminua mais - e até mais - do que o bloco que vocêfree
.Na 3.x
range
não cria uma lista, portanto, o teste acima não cria 10 milhões deint
objetos. Mesmo assim, oint
tipo em 3.x é basicamente um 2.xlong
, que não implementa um freelist.fonte
memory_info()
em vez deget_memory_info()
ex
é definidoint
s mesmo no Python 3, mas cada um substitui a última variável do loop, para que nem todos existam de uma só vez.Suponho que a pergunta com a qual você realmente se importa aqui é:
Não, não há. Mas há uma solução fácil: processos filho.
Se você precisar de 500 MB de armazenamento temporário por 5 minutos, mas depois disso precisará executar por mais 2 horas e não voltará a tocar em tanta memória, inicie um processo filho para fazer o trabalho intensivo em memória. Quando o processo filho desaparece, a memória é liberada.
Isso não é completamente trivial e gratuito, mas é bem fácil e barato, o que geralmente é bom o suficiente para que o comércio valha a pena.
Primeiro, a maneira mais fácil de criar um processo filho é com
concurrent.futures
(ou, para 3.1 e versões anteriores, ofutures
backport no PyPI):Se você precisar de um pouco mais de controle, use o
multiprocessing
móduloOs custos são:
mmap
ped ou não; as APIs de memória compartilhada emmultiprocessing
; etc.).struct
ativados ou idealmentectypes
).fonte
eryksun respondeu à pergunta 1 e eu respondi à pergunta 3 (o original 4), mas agora vamos responder à pergunta 2:
O que se baseia é, finalmente, toda uma série de coincidências dentro do Python e
malloc
que são muito difíceis de prever.Primeiro, dependendo de como você está medindo a memória, você pode estar medindo apenas páginas realmente mapeadas na memória. Nesse caso, sempre que uma página for trocada pelo pager, a memória aparecerá como "liberada", mesmo que não tenha sido liberada.
Ou você pode medir páginas em uso, que podem ou não contar páginas alocadas, mas nunca tocadas (em sistemas que otimizam demais a alocação, como linux), páginas alocadas, mas marcadas
MADV_FREE
, etc.Se você realmente está avaliando as páginas alocadas (o que na verdade não é uma coisa muito útil, mas parece ser o que você está perguntando), e as páginas foram realmente desalocadas, há duas circunstâncias em que isso pode acontecer: você usou
brk
ou equivalente para reduzir o segmento de dados (muito raro hoje em dia) ou você usoumunmap
ou semelhante para liberar um segmento mapeado. (Também há, teoricamente, uma variante menor do último, pois há maneiras de liberar parte de um segmento mapeado - por exemplo, roube-oMAP_FIXED
para umMADV_FREE
segmento que você imediatamente mapeia.)Mas a maioria dos programas não aloca coisas diretamente das páginas da memória; eles usam um
malloc
alocador de estilo. Quando você ligafree
, o alocador só pode liberar páginas para o sistema operacional se você estiverfree
no último objeto ativo em um mapeamento (ou nas últimas N páginas do segmento de dados). Não há como seu aplicativo prever isso razoavelmente ou até detectar que isso aconteceu com antecedência.O CPython torna isso ainda mais complicado - ele possui um alocador de objetos personalizado de dois níveis em cima de um alocador de memória personalizado
malloc
. (Vejo os comentários da fonte para obter uma explicação mais detalhada.) Além disso, mesmo no nível da API C, muito menos no Python, você nem controla diretamente quando os objetos de nível superior são desalocados.Então, quando você libera um objeto, como você sabe se ele libera memória para o sistema operacional? Bem, primeiro você precisa saber que lançou a última referência (incluindo todas as referências internas desconhecidas), permitindo que o GC a desaloque. (Diferentemente de outras implementações, pelo menos o CPython desalocará um objeto assim que permitido.) Isso geralmente desaloca pelo menos duas coisas no próximo nível abaixo (por exemplo, para uma string, você está liberando o
PyString
objeto e o buffer da string )Se vocês fazer desalocar um objeto, para saber se isso faz com que a próxima baixo nível para desalocar um bloco de armazenamento de objetos, você tem que saber o estado interno do objeto alocador, bem como a forma como ele é implementado. (Obviamente, isso não pode acontecer, a menos que você esteja desalocando a última coisa no bloco e, mesmo assim, pode não acontecer.)
Se você fazer desalocar um bloco de armazenamento de objetos, para saber se isso faz com que uma
free
chamada, você tem que saber o estado interno do alocador PyMem, bem como a forma como ele é implementado. (Novamente, você deve desalocar o último bloco em uso dentro de ummalloc
região ed e, mesmo assim, isso pode não acontecer.)Se você faz
free
umamalloc
região ed, para saber se isso causa ummunmap
ou equivalente (oubrk
), você precisa conhecer o estado interno domalloc
e também como ele é implementado. E este, diferentemente dos outros, é altamente específico da plataforma. (E, novamente, você geralmente precisa desalocar o último em usomalloc
em ummmap
segmento e, mesmo assim, isso pode não acontecer.)Portanto, se você quiser entender por que lançou exatamente 50,5mb, precisará rastrear de baixo para cima. Por que
malloc
desmapear 50.5mb no valor de páginas quando você fez uma ou maisfree
chamadas (por provavelmente um pouco mais que 50.5mb)? Você precisaria ler a plataformamalloc
e percorrer as várias tabelas e listas para ver seu estado atual. (Em algumas plataformas, pode até fazer uso de informações no nível do sistema, o que é praticamente impossível de capturar sem fazer uma captura instantânea do sistema para inspecionar offline, mas felizmente isso geralmente não é um problema.) E então você precisa faça a mesma coisa nos 3 níveis acima disso.Portanto, a única resposta útil para a pergunta é "Porque".
A menos que você esteja desenvolvendo recursos limitados (por exemplo, incorporados), não há motivos para se preocupar com esses detalhes.
E se você estiver desenvolvendo recursos limitados, conhecer esses detalhes é inútil; você praticamente precisa executar uma execução final em todos esses níveis e, especificamente,
mmap
na memória necessária no nível do aplicativo (possivelmente com um alocador de zona específico do aplicativo, simples e bem compreendido).fonte
Primeiro, você pode querer instalar olhares:
Em seguida, execute-o no terminal!
No seu código Python, adicione no início do arquivo o seguinte:
Depois de usar a variável "Big" (por exemplo: myBigVar) para a qual você gostaria de liberar memória, escreva no seu código python o seguinte:
Em outro terminal, execute seu código python e observe no terminal "glances" como a memória é gerenciada no seu sistema!
Boa sorte!
PS Presumo que você esteja trabalhando em um sistema Debian ou Ubuntu
fonte