Introdução
O uso de memória abaixo do ideal afeta o desempenho de códigos e é um obstáculo comum para o bom funcionamento de programas. Mas ele pode ser sistematicamente aprimorado. Neste post, abordaremos como melhorar o desempenho dos seus códigos Python através da coleta de lixo eficiente.
O que é coleta de lixo?
Em Python, a coleta de lixo (garbage collection) é um mecanismo que identifica e libera automaticamente a memória que não é mais necessária em um programa. A coleta de lixo automatiza a liberação de memória quando um objeto não está mais em uso. Com isso, ela auxilia no gerenciamento de memória e evita seus vazamentos. Quando não prevenidos, vazamentos de memória causam problemas de desempenho e travamentos.
Gerenciamento de memória e armazenamento
Para entendermos como funciona a coleta de lixo, precisamos compreender como o Python armazena objetos em sua memória. Objetos Python têm três características: tipo, valor e contagem de referências. Quando atribuímos um nome a uma variável, o Python detecta automaticamente seu tipo. Ou seja, o Python é uma linguagem tipada dinamicamente. Ele indica o tipo de variável no tempo de execução do programa. O valor da variável é declarado ao definir o objeto. A contagem de referência é o número de nomes que apontam para esse objeto. Uma referência é um nome (ponteiro) que se refere a um objeto.
Formas de coleta de lixo em Python
O Python usa dois métodos principais de coleta de lixo: contagem de referência e geracional.
O principal mecanismo de coleta de lixo em Python se baseia na contagem de referências. Cada objeto em Python tem uma contagem de referências. Ela rastreia o número de referências a um objeto. Quando a contagem de referência de um objeto atinge zero, o objeto não está mais em uso e pode ser desalocado da memória com segurança.
O coletor de lixo do Python também emprega coleta geracional. Essa técnica divide os objetos em diferentes gerações e os coleta de acordo. A coleta de lixo geracional em Python consiste em três gerações: jovem, média e velha ou listas de geração 0, 1 e 2. Os objetos recém-criados são colocados na lista Geração 0 (jovem). Uma lista é criada para os objetos serem descartados e ciclos de referência serem detectados. Se um objeto não tiver referências externas, ele será descartado. Os objetos que sobrevivem a esse processo são colocados na lista de geração 1. As mesmas etapas descritas para a geração 0 são aplicadas à lista da geração 1 (média). Os sobreviventes da lista da geração 1 são colocados na lista da geração 2 (velha). Os objetos na lista da Geração 2 permanecem lá até o final da execução do programa.
Vazamentos de memória e desempenho
Quando objetos mantêm suas memórias alocadas quando já não são mais usados, o desempenho e a estabilidade dos programas são afetados. Vazamentos de memória são ocorrências comuns e uma grande causa de desempenho ruim em programas. Eles se acumulam silenciosamente à medida que um código continua a ser executado. Esse acúmulo consome gradualmente recursos preciosos até eles se tornarem um gargalo.
Uma causa frequente dos vazamentos são as referências circulares. Elas ocorrem quando dois ou mais objetos fazem referências recíprocas entre si. Nesses casos, a coleta tem dificuldades para identificar esses objetos como lixo. Como consequência, eles ficam retidos na memória.
Recursos não fechados, coleções estáticas que crescem indefinidamente, bugs em bibliotecas ou middleware de terceiros são outras causas comuns de vazamentos de memória. Todas elas podem comprometer o desempenho de códigos Python.
Para evitar essas situações, podemos usar o módulo gc integrado do Python. Ele permite acionar manualmente a coleta de lixo e monitorar o uso da memória. A coleta manual de lixo pode ajudar a liberar memória mais rapidamente. Ela também pode auxiliar na identificação de vazamentos de memória.
módulo GC do Python
O Python emprega um sistema robusto de coleta de lixo que libera automaticamente a memória ocupada por objetos que não estão mais em uso. Este sistema consiste em dois componentes principais:
Ciclo Menor: se concentra em liberar memória de pequenos objetos. Ele é executado continuamente para lidar com a maioria das tarefas diárias.
Ciclo Principal: focado em objetos maiores. Este ciclo é executado quando o sistema detecta uma quantidade significativa de memória não coletada.
Na maior parte das situações, a coleta automática de lixo do Python é suficiente. Mas, em algumas situações, o uso de coleta manual é benéfico.
coleta manual de lixo
Para otimizar códigos Python, podemos acionar manualmente o modulo gc para realizar a coleta de lixo e monitorar o uso de memória. Veja um exemplo de como usá-lo:
import gc
import os
# Aciona manualmente a coleta de lixo
gc.collect()
# Monitora o uso de memória
print(f"Uso de memória: {os.sys.getsizeof('SEU OBJETO AQUI')} bytes")
O método gc.collect() força a coleta de lixo. Ele pode ser usado em códigos Python para liberar rapidamente a memória usada por objetos grandes. Por exemplo, ao treinar um modelo de machine learning com um grande conjunto de dados, você pode acioná-lo para liberar a memória mais rapidamente.
Mas essa não é a única situação onde a coleta manual é benéfica. Qualquer parte de um código que libere grandes blocos de memória é um bom candidato para executar a coleta manual de lixo.
Outro parâmetro interessante para se ter a mão é o limite (treshold) da coleta de lixo. Toda vez que o número de alocações menos o número de desalocações é maior que o limite da coleta, o coletor de lixo é executado. O módulo cg possui um limite padrão, mas ele pode ser personalizado. Para conhecer o limite, use o método get_threshold():
import gc
print('Limite do coletor:', gc.get_threshold())
# resultado: Limite do coletor: (700, 10, 10)
Para alterar o valor padrão, use o método set_treshold():
gc.set_threshold(500, 5, 5) # muda limite para 500
Conclusão
A coleta de lixo em Python é um aspecto importante para garantir o bom desempenhos dos programas. Se seus códigos usam dados grandes ou são executados em sistemas com recursos limitados, calibre a coleta de lixo manual após seções de código que usam e liberam grandes blocos de memória. Use gc.collect() também após operações que criam e descartam muitos objetos para liberar memória mais rapidamente. Mas lembre-se: embora a coleta de lixo seja benéfica em muitos casos, ela deve ser usada com moderação. Não execute gc.collect() com muita frequência. Ao ser usado frequentemente, ele pode afetar o desempenho de seu sistema.
Veja também:
Concatenações com join() para strings em Python
O que é operador ternário em Python?
F-strings em Strings Multilinhas
Decodificação de strings em Python com decode()
Métodos para Manipular Strings em Python
Módulo Getpass para Prompts de Senhas
Aprenda a comparar textos com Python com Difflib
Módulo textwrap para formatação de textos
Manipulação de arquivos com Python
os.environ: gerenciamento de variáveis de ambiente com Python
Métodos Avançados Do Módulo os
Aprenda a criar documentos do Word com Python
Três dicas simples para escrever códigos Python mais robustos
Encontrou algum erro ou quer fazer uma sugestão? Por favor, entre em contato usando nosso formulário de contatos.