Como posso liberar explicitamente memória no Python?

388

Eu escrevi um programa Python que atua em um grande arquivo de entrada para criar alguns milhões de objetos representando triângulos. O algoritmo é:

  1. ler um arquivo de entrada
  2. processe o arquivo e crie uma lista de triângulos, representada por seus vértices
  3. produz os vértices no formato OFF: uma lista de vértices seguida por uma lista de triângulos. Os triângulos são representados por índices na lista de vértices

O requisito de OFF que eu imprima a lista completa de vértices antes de imprimir os triângulos significa que tenho que manter a lista de triângulos na memória antes de gravar a saída no arquivo. Enquanto isso, estou recebendo erros de memória por causa dos tamanhos das listas.

Qual é a melhor maneira de dizer ao Python que não preciso mais de alguns dados e que eles podem ser liberados?

Nathan Fellman
fonte
11
Por que não imprimir os triângulos em um arquivo intermediário e lê-los novamente quando precisar deles?
Alice Purcell
2
Essa questão pode ser potencialmente sobre duas coisas bem diferentes. Esses erros são do mesmo processo Python ; nesse caso, nos preocupamos em liberar memória para o heap do processo Python, ou eles são de processos diferentes no sistema, caso em que nos importamos em liberar memória para o sistema operacional?
Charles Duffy

Respostas:

456

De acordo com a documentação oficial do Python , você pode forçar o Garbage Collector a liberar memória não referenciada gc.collect(). Exemplo:

import gc
gc.collect()
Havenard
fonte
19
As coisas são coletadas com frequência com frequência, exceto em alguns casos incomuns, então não acho que isso ajude muito.
Lennart Regebro
24
Em geral, gc.collect () deve ser evitado. O coletor de lixo sabe como fazer seu trabalho. Dito isto, se o OP estiver em uma situação em que ele repentinamente está desalocando muitos objetos (como os milhões), o gc.collect pode ser útil.
22630 Jason Baker
165
Chamar a gc.collect()si mesmo no final de um loop pode ajudar a evitar a fragmentação da memória, o que, por sua vez, ajuda a manter o desempenho. Eu já vi isso faz uma diferença significativa (~ 20% de tempo de execução IIRC)
RobM
39
Estou usando python 3.6. Chamando gc.collect()depois de carregar uma trama de dados de pandas de HDF5 (500k linhas) reduziu o uso de memória de 1.7 GB para 500 MB
John
15
Preciso carregar e processar várias matrizes numpy de 25 GB em um sistema com 32 GB de memória. Usar del my_arrayseguido de gc.collect()depois do processamento da matriz é a única maneira pela qual a memória é realmente liberada e meu processo sobrevive para carregar a próxima matriz.
David
113

Infelizmente (dependendo da sua versão e lançamento do Python), alguns tipos de objetos usam "listas gratuitas", que são uma otimização local pura, mas podem causar fragmentação da memória, especificamente tornando cada vez mais a memória "reservada" apenas para objetos de um determinado tipo e portanto indisponível para o "fundo geral".

A única maneira realmente confiável de garantir que um uso amplo, mas temporário de memória, devolva todos os recursos ao sistema quando terminar, é fazer com que esse uso ocorra em um subprocesso, que finaliza o trabalho que requer muita memória. Sob tais condições, o sistema operacional fará seu trabalho e alegremente reciclará todos os recursos que o subprocesso pode ter consumido. Felizmente, o multiprocessingmódulo torna esse tipo de operação (que costumava ser uma dor) não muito ruim nas versões modernas do Python.

No seu caso de uso, parece que a melhor maneira de os subprocessos acumularem alguns resultados e ainda garantir que esses resultados estejam disponíveis para o processo principal é usar arquivos semi-temporários (por semi-temporário, quero dizer, NÃO o tipo de arquivo que desaparecem automaticamente quando fechadas, apenas os arquivos comuns que você exclui explicitamente quando termina o processo).

Alex Martelli
fonte
31
Eu com certeza gostaria de ver um exemplo trivial disso.
Aaron Hall
3
A sério. O que @AaronHall disse.
Noob Saibot
17
@AaronHall Exemplo trivial agora disponível , usando, em multiprocessing.Managervez de arquivos, para implementar o estado compartilhado.
user4815162342
48

A deldeclaração pode ser útil, mas o IIRC não garante a liberação da memória . Os documentos estão aqui ... e um motivo pelo qual não foi divulgado está aqui .

Eu ouvi pessoas nos sistemas Linux e Unix do tipo bifurcando um processo python para fazer algum trabalho, obter resultados e depois matá-lo.

Este artigo possui notas sobre o coletor de lixo Python, mas acho que a falta de controle de memória é a desvantagem da memória gerenciada

Aiden Bell
fonte
IronPython e Jython seriam outra opção para evitar esse problema?
Esteban Küber 22/08/09
@ voyager: Não, não seria. E nem qualquer outra língua, realmente. O problema é que ele lê grandes quantidades de dados em uma lista e os dados são muito grandes para a memória.
Lennart Regebro
11
Provavelmente seria pior no IronPython ou Jython. Nesses ambientes, você nem garante que a memória será liberada se nada mais estiver mantendo uma referência.
22430 Jason Baker
@voyager, sim, porque a máquina virtual Java procura globalmente a memória para liberar. Para a JVM, o Jython não é nada de especial. Por outro lado, a JVM possui sua própria parte de desvantagens, por exemplo, que você deve declarar antecipadamente o tamanho da pilha que pode usar.
O contrato do Prof. Falken violou
32

O Python é coletado pelo lixo, portanto, se você reduzir o tamanho da sua lista, ele recuperará a memória. Você também pode usar a instrução "del" para se livrar completamente de uma variável:

biglist = [blah,blah,blah]
#...
del biglist
Ned Batchelder
fonte
18
Isso é e não é verdade. Embora a diminuição do tamanho da lista permita a recuperação da memória, não há garantia de quando isso acontecerá.
user142350
3
Não, mas geralmente isso ajuda. No entanto, como eu entendo a pergunta aqui, o problema é que ele precisa ter tantos objetos que fica sem memória antes de processá-los, se os lê em uma lista. A exclusão da lista antes que ele termine o processamento provavelmente não será uma solução útil. ;)
Lennart Regebro 22/08/09
3
Uma condição de pouca memória / falta de memória não acionaria uma "execução de emergência" do coletor de lixo?
Jeremy Friesner 22/08/09
4
biglist = [] liberará memória?
neouyghur
3
Sim, se a lista antiga não for referenciada por mais nada.
Ned Batchelder
22

Você não pode liberar explicitamente a memória. O que você precisa fazer é garantir que não mantenha referências a objetos. Eles serão coletados como lixo, liberando a memória.

No seu caso, quando você precisa de listas grandes, normalmente precisa reorganizar o código, geralmente usando geradores / iteradores. Dessa forma, você não precisa ter as grandes listas na memória.

http://www.prasannatech.net/2009/07/introduction-python-generators.html

Lennart Regebro
fonte
11
Se essa abordagem for viável, provavelmente vale a pena fazer. Mas deve-se notar que você não pode fazer acesso aleatório nos iteradores, o que pode causar problemas.
22630 Jason Baker
Isso é verdade e, se necessário, o acesso a grandes conjuntos de dados aleatoriamente provavelmente requer algum tipo de banco de dados.
Lennart Regebro
Você pode facilmente usar um iterador para extrair um subconjunto aleatório de outro iterador.
S.Lott
É verdade, mas você precisaria percorrer tudo para obter o subconjunto, o que será muito lento.
Lennart Regebro
21

(del pode ser seu amigo, pois marca os objetos como deletáveis ​​quando não há outras referências a eles. Agora, geralmente o intérprete do CPython mantém essa memória para uso posterior, para que seu sistema operacional não veja a memória "liberada".)

Talvez você não tenha problemas de memória em primeiro lugar usando uma estrutura mais compacta para seus dados. Assim, as listas de números são muito menos eficientes em termos de memória do que o formato usado pelo arraymódulo padrão ou pelo numpymódulo de terceiros . Você economizaria memória colocando seus vértices em uma matriz NumPy 3xN e seus triângulos em uma matriz de elementos N.

Eric O Lebigot
fonte
Eh? A coleta de lixo do CPython é baseada em refcounting; não é uma marcação e varredura periódica (como em muitas implementações comuns da JVM), mas exclui imediatamente algo no momento em que sua contagem de referência chega a zero. Somente ciclos (onde refcounts seria zero, mas não são por causa de loops na árvore de referência) requerem manutenção periódica. delnão faz nada que apenas reatribuir um valor diferente a todos os nomes que referenciam um objeto.
Charles Duffy
Vejo de onde você é: atualizarei a resposta de acordo. Entendo que o interpretador CPython realmente funciona de alguma maneira intermediária: dellibera a memória do ponto de vista do Python, mas geralmente não do ponto de vista da biblioteca de tempo de execução C ou do SO. Referências: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/… .
Eric O Lebigot
Concordamos com o conteúdo dos seus links, mas supondo que o OP esteja falando de um erro que eles obtêm do mesmo processo Python , a distinção entre liberar memória para a pilha local do processo e para o sistema operacional não parece ser relevante ( liberar para o heap torna esse espaço disponível para novas alocações dentro desse processo Python). E, para isso, delé igualmente eficaz com saídas fora do escopo, reatribuições etc.
Charles Duffy
11

Eu tive um problema semelhante ao ler um gráfico de um arquivo. O processamento incluiu o cálculo de uma matriz flutuante de 200 000 x 200 000 (uma linha de cada vez) que não cabia na memória. Tentando liberar a memória entre os cálculos usando gc.collect()o aspecto relacionado à memória do problema, mas resultou em problemas de desempenho: não sei por que, embora a quantidade de memória usada permanecesse constante, cada nova chamada gc.collect()levava mais tempo do que o anterior. Tão rapidamente que a coleta de lixo levou a maior parte do tempo computacional.

Para corrigir os problemas de memória e desempenho, mudei para o uso de um truque de multithreading que li uma vez em algum lugar (desculpe, não consigo mais encontrar a postagem relacionada). Antes eu estava lendo cada linha do arquivo em um forloop grande , processando-o e executando de gc.collect()vez em quando para liberar espaço de memória. Agora, chamo uma função que lê e processa um pedaço do arquivo em um novo thread. Quando o segmento termina, a memória é automaticamente liberada sem o problema de desempenho estranho.

Praticamente funciona assim:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided
Retzod
fonte
11
Gostaria de saber por que você está usando `//` `s em vez de # em Python para comentários.
JC Rocamonde
Eu me misturei entre os idiomas. Obrigado pela observação, atualizei a sintaxe.
Retzod
9

Outros publicaram algumas maneiras pelas quais você pode "convencer" o interpretador Python a liberar a memória (ou evitar outros problemas de memória). Provavelmente, você deve experimentar suas idéias primeiro. No entanto, acho importante dar uma resposta direta à sua pergunta.

Não há realmente nenhuma maneira de dizer diretamente ao Python para liberar memória. O fato é que, se você deseja um nível de controle tão baixo, precisará escrever uma extensão em C ou C ++.

Dito isto, existem algumas ferramentas para ajudar com isso:

Jason Baker
fonte
3
GC.Collect () e del gc.garbage [:] funcionam muito bem quando estou usando grandes quantidades de memória
Andrew Scott Evans
3

Se você não se importa com a reutilização de vértices, pode ter dois arquivos de saída - um para vértices e outro para triângulos. Acrescente o arquivo triângulo ao arquivo de vértice quando terminar.

Nosredna
fonte
11
Eu acho que posso manter apenas os vértices na memória e imprimir os triângulos em um arquivo e, em seguida, imprimir os vértices apenas no final. No entanto, o ato de gravar os triângulos em um arquivo é um grande prejuízo para o desempenho. Existe alguma maneira de acelerar isso ?
23419 Nathan Fellman