Um pouco sobre Garbage Collector

Fala galera.

Nesses últimos meses tenho trabalhado bastante com otimização de performance em aplicações .NET e gostaria de compartilhar um pouco da minha experiência nessa empreitada.

Problemas de performance geralmente estão relacionados a falta de cuidado ao codificar aliado, em muito casos, à falta de conhecimento.

Nesse post falaremos um pouco sobre o Garbage Colletor.

Apesar de ser um forte aliado, o GC pode acabar sendo um grande vilão da performance da aplicação.

Estudando sobre o GC encontrei essa frase:

“There’s no GC if you don’t allocate. Reducing allocations is key to reducing GC Costs.”

Ajude o Garbage Collector a te ajudar. Aloque menos memória possível!

Lembram das funções malloc e free do C? Pois é… graças ao Garbage Collector não precisamos gerenciar a memória manualmente como era feito no C. Mas a comodidade sempre traz um custo.

Vamos aos conceitos básicos a respeito do Garbage Collector no .NET.

O GC sempre faz 2 varreduras chamadas de Mark and Sweep. Essas ações são fundamentais para recuperação de memória.

  • Mark: Identificar objetos vivosheap
    • O objetivo nessa fase é identificar os objetos que estão vivos na memória heap. Esse processo é feito recursivamente.  O GC mantém o controle dos objetos que já foram marcados afim de evitar um loop infinito num ciclo de referência entre objetos.

 

 

  • Sweep: Recuperar objetos mortosheap sweep
    • Nessa fase o GC recupera toda a memória ocupada com objetos que já não estão sendo referenciados.

 

 

 

  • Compact: Deixar os objetos vivos juntosheap compact
    • Depois das duas primeiras fases o GC junta todos os objetos vivos em espaços consecultivos na memória heap. Isso ajuda na performance. É uma espécie de desfragmentador da memória.

Basicamente o trabalho do GC é “só” esse. Parece fácil né? Mas está longe disso!

Na verdade a primeira fase é a mais pesada e requer bastante processamento e inteligência. Isso porque para identificar os objetos que estão vivos o GC leva em consideração os objetos que estão ligados aos roots. Vamos entender o que são roots:

Roots são basicamente variáveis de pilha (stack) / ponteiros que mantém objetos na memória heap. Roots podem também ser variáveis globais. Resumindo: Qualquer ponteiro (que não resida na heap) pra algum objeto na heap pode ser considerado um root.

Ok. Explicado como o GC trabalha internamente vamos entender como o configurá-lo.

O GC pode rodar em dois modos: Concorrente e não concorrente.

  • Concorrente: Modo padrão (somente a partir da versão 4.5)
    • Esse é o modo padrão. O CLR cria uma thread especial só para o GC assim que o seu aplicativo é iniciado. Essa thread monitora a heap e realiza coletas de lixo ocasionalmente. Ela também agenda a coleta de maneira a não degradar tanto a performance do aplicativo.
    • Mas o ponto mais importante é que, na maior parte do tempo o GC roda juntamente com as threads da aplicação, apenas com pequenas interrupções.
    • As vezes o GC suspende todas as threads da aplicação mas geralmente essas suspensões são curtas.
  • Não-Concorrente
    • Não há thread especial para o GC. Todas as threads do aplicativo são suspensas durante a coleta de lixo.

Para configurar é só usar a tag <gcConcurrent>.

<configuration>
   <runtime>
       <gcConcurrent enabled="false"/>
   </runtime>
</configuration>

O fato do Garbage Collector ter uma thread só pra ele já é sensacional né? Agora imagine se pudéssemos rodar várias instâncias do GC simultaneamente, uma pra cada processador lógico?
Isso é possível através da tag <GCServer>. Veja abaixo:

<configuration>
   <runtime>
      <gcServer enabled="true"/>
   </runtime>
</configuration>

A grande vantagem do modo servidor é que ele tem uma heap separada para cada processador lógico.

Enfim… Devemos considerar habilitar o GCServer, mas sempre fazendo a medição pra ter certeza que o resultado é satisfatório.

Anúncios