Início > Desenvolvimento de Software > Tentando otimizar códigos C# (parte 2)

Tentando otimizar códigos C# (parte 2)

Em C e C++ temos ponteiros, em C# e Java temos referências. Ok, em C++ temos referências também, mas num contexto um pouco diferente. Afinal, o que são as referências em linguagens gerenciadas?

Colocando de forma simples. Referências são ponteiros para ponteiros. Quando você cria uma instância de um objeto um ponteiro para a região da memória onde o objeto foi colocado é fornecido. Somente assim podemos localizar o objeto na memória. O detalhe de C# e Java é que graças ao algorítmo de compressão do heap (aquele que evita fragmentação de memória), os ponteiros para objetos podem ser modificados a qualquer momento. A maneira mais fácil de lidar com isso é criar uma tabela separada contendo os ponteiros para os objetos e fazem com que algo aponte para uma entrada nessa tabela – uma entrada nessa tabela é conhecida como raiz. Esse algo é uma referência. Dessa forma, o GC pode mover o objeto para onde quiser que as referências para o objeto continuarão apontando para a mesma entrada da tabela de ponteiros para os objetos reais.

Seria ótimo se os objetos no .NET implementassem um recurso conhecido como reference counting. Contar referências é manter um contador em cada objeto (ou entrada na tabela de ponteiros) para cada referência que aponte para a entrada na tabela de ponteiros, assim, o GC poderia decidir se um objeto pode ser liberado ou não mais rapidamente. No entanto, o MSDN diz claramente (veja aqui):

Unlike COM, the common language runtime does not use reference counting to govern object lifetime. Instead, the garbage collector traces object references and identifies objects that can no longer be accessed by running code.

Como o .NET faz esse “tracing” é um mistério que só a Microsoft entende. De fato, o mesmo artigo diz:

The only potential disadvantage of traced garbage collection is the interval between the release of the last reference that can be reached by running code, and the moment when the garbage collector detects this condition. On a lightly loaded system with a great deal of memory, considerable time may elapse before your component’s destructor is called.

Que só atesta tudo o que falei nos textos anteriores.

Chamei esse post de “tentativa” porque tentei verificar a alegação de que o GC do .NET não usa reference counting criando uma referência para um objeto, anulando essa referência, executando um loop demorado em seguida e verificando se o objeto era liberado no meio do loop… Não acontece… De fato, .NET não usa reference counting – ou parece não usar. Me pareceu uma boa idéia (que resolveria, parcialmente, os problemas que relatei). Os motivos da Microsoft por não usar isso parecem estar relacionados a estruturas de listas circulares, onde o último item da lista conteria uma referência para o primeiro… ao liberar a referência principal, ainda teríamos a referência do último link, arruinando a tentativa de resolver o problema do GC.

Parece mesmo que o .NET varre a lista de referências a procura de alguma que seja inválida, nula ou que simplesmente seja “velha”. Como isso é feito é um detalhe mantido pela Microsoft. A dica que posso dar (e que não funciona assim tão bem) é assinalar null para referência que estão em desuso, ajudando o GC a coletar os objetos que eram referenciados por ela. Isso só é útil antes de algum processamento que tome muito tempo (antes de um loop longo, por exemplo). Mas, mesmo dando essa mão para o GC, a liberação de recursos ainda não é predizível.

  1. Nenhum comentário ainda.
  1. No trackbacks yet.