Cada instância de uma classe no CPython criada usando a sintaxe da classe está envolvida em um mecanismo circular de coleta de lixo . Isso aumenta a área de cobertura da memória de cada instância e pode causar problemas de memória em sistemas muito carregados.
Se necessário, é possível dispensar um mecanismo básico de contagem de links ?
Vamos dar uma olhada em uma abordagem que ajudará a criar classes cujas instâncias serão excluídas apenas usando o mecanismo de contagem de links .
Um pouco sobre a coleta de lixo no CPython
O principal mecanismo de coleta de lixo no Python é o mecanismo de contagem de links. Cada objeto contém um campo que contém o valor atual do número de links para ele. Um objeto é destruído assim que o valor do contador de referência se torna zero. No entanto, ele não permite o descarte de objetos que contenham referências circulares. Por exemplo
lst = [] lst.append(lst) del lst
Nesses casos, após a exclusão do objeto, o contador de referências a ele permanece mais que zero. Para resolver esse problema, o Python possui um mecanismo adicional que rastreia objetos e quebra os loops no gráfico de links entre os objetos. Há um bom artigo sobre como o mecanismo de coleta de lixo funciona no CPython3.
Sobrecarga associada ao mecanismo de coleta de lixo
Normalmente, o mecanismo de coleta de lixo não causa problemas. Mas existem certas despesas gerais associadas a ele:
Quando cada classe de memória é alocada, o cabeçalho PyGC_Head é adicionado : (pelo menos 24 bytes em Python <= 3.7 e pelo menos 16 bytes em 3.8 em uma plataforma de 64 bits.
Isso pode criar um problema de falta de memória se você executar muitas instâncias do mesmo processo, nas quais você precisa ter um número muito grande de objetos com um número relativamente pequeno de atributos e o tamanho da memória é limitado.
Às vezes é possível limitar-se ao mecanismo básico de contagem de links?
O mecanismo de coleta de lixo circular pode ser redundante quando a classe representa um tipo de dados não recursivo. Por exemplo, registros contendo valores de um tipo simples (números, seqüências de caracteres, data / hora). Para ilustrar, considere uma classe simples:
class Point: x: int y: int
Se usado corretamente, os ciclos de link não são possíveis. Embora em Python, nada impede "chutar o pé":
p = Point(0, 0) px = p
No entanto, para a classe Point
, pode-se restringir-se a um mecanismo de contagem de links. Mas ainda não existe um mecanismo padrão para rejeitar a coleta de lixo cíclico para uma única classe.
O CPython moderno é projetado para que, ao definir classes personalizadas na estrutura responsável pelo tipo que define a classe personalizada, o sinalizador Py_TPFLAGS_HAVE_GC seja sempre definido. Determina que as instâncias de classe serão incluídas no mecanismo de coleta de lixo. Para todos esses objetos, quando criados, o cabeçalho PyGC_Head é adicionado e eles são incluídos na lista de objetos monitorados. Se o sinalizador Py_TPFLAGS_HAVE_GC
não Py_TPFLAGS_HAVE_GC
definido, apenas o mecanismo básico de contagem de links funcionará. No entanto, uma única redefinição de Py_TPFLAGS_HAVE_GC
não funcionará. Você precisará fazer alterações no CPython principal responsável por criar e destruir instâncias. E isso ainda é problemático.
Sobre uma implementação
Como um exemplo da implementação da ideia, considere o objeto de dados da classe base do projeto da classe de registro . Utilizando-o, é possível criar classes cujas instâncias não participam do mecanismo de coleta de lixo cíclico ( Py_TPFLAGS_HAVE_GC
não Py_TPFLAGS_HAVE_GC
instalado e, portanto, não há cabeçalho PyGC_Head
adicional). Eles possuem exatamente a mesma estrutura na memória que as instâncias de classe com __slots__ , mas sem o PyGC_Head
:
from recordclass import dataobject class Point(dataobject): x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 32
Para comparação, damos uma classe semelhante com __slots__
:
class Point: __slots__ = 'x', 'y' x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 64
A diferença de tamanho é exatamente o tamanho do cabeçalho PyGC_Head
. Para instâncias com vários atributos, esse aumento no tamanho de seu rastreamento na RAM pode ser significativo. Para instâncias da classe Point
, adicionar PyGC_Head
aumentará seu tamanho em 2 vezes.
Para alcançar esse efeito, datatype
um datatype
metaclasse especial, que fornece a configuração de subclasses do dataobject
. Como resultado da configuração, o sinalizador Py_TPFLAGS_HAVE_GC
é Py_TPFLAGS_HAVE_GC
, o tamanho base da instância tp_basicsize aumenta pela quantidade necessária para armazenar slots de campo adicionais. Os nomes dos campos correspondentes são listados quando a classe é declarada (a classe Point
possui dois: x
e y
). O datatype
metatlass também fornece a configuração dos valores dos slots tp_alloc , tp_new , tp_dealloc , tp_free , que implementam os algoritmos corretos para criar e destruir instâncias na memória. Por padrão, as instâncias não possuem __weakref__ e __dict__ (como instâncias de classe com __slots__
).
Conclusão
Como se pode ver, no CPython, se necessário, é possível desativar o mecanismo de coleta de lixo circular para uma classe específica, quando há confiança de que suas instâncias não formarão links circulares. Isso reduzirá seu rastreamento na memória pelo tamanho do cabeçalho PyGC_Head
.