Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

RSS iMasters

[Resolvido] Melhorando o desempenho do aplicativo .NET - Parte 04

Recommended Posts

Este artigo faz parte de uma série sobre o desempenho do aplicativo .NET. Os três primeiros artigos já estão disponíveis: Parte 01; Parte 02; Parte 03.

 

Neste artigo discutiremos as diretrizes do coletor de lixo. O Framework. NET utiliza o coletor de lixo automático para gerenciar a memória para todos os aplicativos. Quando você usa o operador New para criar um objeto, a memória do objeto é obtida a partir do heap gerenciado. Quando o coletor de lixo decide que o lixo acumulado já é o suficiente, ele executa uma coleta para liberar memória. Este processo é totalmente automático, mas há uma série de coisas que você precisa estar ciente.

Vamos dar uma olhada no ciclo de vida de um objeto gerenciado:

  1. A memória de um objeto é alocada a partir do heap gerenciado quando você chamar o New. O construtor do objeto é chamado após a memória ser alocada;
  2. O objeto é usado por um período de tempo;
  3. O objeto morre devido a todas as suas referências, seja explicitamente definidas, como null, ou então saindo do escopo;
  4. A memória do objeto é liberada (coletada) algum tempo depois. Após a memória ser liberada, está disponível para outros objetos.

Alocação

O heap gerenciado pode ser pensado como um bloco de memória contígua. Quando você cria um novo objeto, a sua memória é alocada na próxima posição disponível no heap gerenciado. Já que o coletor de lixo não precisa se procurar com espaço, as atribuições são extremamente rápidas, caso haja memória suficiente disponível. Se não houver para o novo objeto, o coletor de lixo tenta recuperar espaço para ele.

Coletor

Para recuperar o espaço, o coletor de lixo recolhe objetos que não estão mais acessíveis. Um objeto não é mais acessível quando não há referências a ele, todas elas são definidas como null, ou todas as referências a ele são de outros objetos, que podem ser coletados como parte do ciclo atual de coleta.

 

Quando uma coleta ocorre, os objetos acessíveis são traçados e marcados como recursos de rastreamento. O coletor de lixo recupera o espaço movendo objetos acessíveis no espaço contíguo e recuperando a memória usada pelos objetos inacessíveis. Qualquer objeto que sobrevive à coleta é promovido para a próxima geração.

Gerações

O coletor de lixo utiliza três gerações para agrupar objetos por sua vida útil e volatilidade:

  • Geração 0 - Consiste em objetos recém-criados. Gen 0 é recolhido com frequência para garantir que objetos de vida curta sejam rapidamente limpos. Esses objetos que sobrevivem a uma coleta Gen 0 são promovidos a Geração 1;
  • Geração 1 - Esta é coletada com menos frequência do que o Gen 0 e contém mais objetos de vida longa que foram promovidos a partir de Gen 0;
  • Geração 2 - Esta contém objetos promovidos da Gen 1 (o que significa que contém os objetos de vida mais longa) e é coletada com menos frequência ainda. A estratégia geral do coletor de lixo é recolher e mover objetos de vida longa com menos frequência.

Orientações para coletores de lixo

Nesta seção iremos lhe dar algumas orientações para melhorar o desempenho da coleta de lixo.

  • Analise o perfil do seu aplicativo de alocação ? Tamanho, número e vida útil dos objetos são fatores que impactam o perfil do seu aplicativo de alocação. Enquanto as alocações são rápidas, a eficiência da coleta de lixo depende (entre outras coisas) da geração que está sendo coletada. A coleta de pequenos objetos a partir da Gen 0 é a forma mais eficiente de coleta de lixo, porque a Gen 0 é a menor e, normalmente, se encaixa no cache da CPU. Em contraste, a coleta frequênte de objetos de Gen 2 é custosa. Para identificar quando as alocações ocorrem e em quais gerações elas ocorrem, observe os padrões de seu aplicativo de alocação, usando um profiler de alocação, como o CLR Profiler.
  • Evite chamar o GC.Collect - O método GC.Collect padrão provoca uma coleta completa de todas as gerações, o que é custoso, porque, literalmente, todos os objetos vivos no sistema devem ser visitados para garantir a coleta completa. Visitar exaustivamente todos os objetos vivos geralmente leva uma quantidade significativa de tempo. O coletor de lixo é ajustado de modo que faça coletas completas apenas quando vale a pena o custo de fazê-lo. Como resultado, não chame o GC.Collect diretamente - deixe o coletor de lixo determinar quando ele precisa ser executado.

     

    O coletor de lixo é projetado para ser auto ajustar e ele ajusta sua operação para atender às necessidades do seu aplicativo com base na pressão de memória. Forçar programaticamente a coleta pode dificultar o ajuste e a operação do coletor de lixo.

  • Considere utilizar referências fracas com dados em cache - Considere o uso de referências fracas quando trabalhar com dados em cache, de modo que objetos em cache possam ser ressuscitados facilmente, se necessário, ou liberados pela coleta de lixo. Use referências fracas principalmente para objetos que não são pequenos em tamanho, porque a referência fraca em si envolve uma sobrecarga. Eles são adequados para objetos de médio e grande porte armazenados em uma coleta.

Se em uma pesquisa de cache seguinte, você não puder encontrar o objeto, recrie-o a partir da informação armazenada em uma fonte autorizada persistente. Desta forma, você equilibra o uso de cache. O código a seguir demonstra como usar uma referência fraca.

void SomeMethod()

 

{

 

// Create a collection

 

ArrayList arr = new ArrayList(5);

 

 

 

// Create a custom object

 

MyObject mo = new MyObject();

 

 

 

// Create a WeakReference object from the custom object

 

WeakReference wk = new WeakReference(mo);

 

 

 

// Add the WeakReference object to the collection

 

arr.Add(wk);

 

 

 

// Retrieve the weak reference

 

WeakReference weakReference = (WeakReference)arr[0];

 

MyObject mob = null;

 

if( weakReference.IsAlive ) {

 

mob = (MyOBject)weakReference.Target;

 

}

 

if(mob==null) {

 

// Resurrect the object as it has been garbage collected

 

}

 

//continue because we have the object

 

}

  • Prevenir a promoção de objetos de vida curta - Os objetos que são alocados e recolhidos antes de deixar a Gen 0 são conhecidos como objetos de vida curta. Os princípios a seguir ajudam a garantir que seus objetos de vida curta não sejam promovidos:
  1. Não referenciar objetos de vida curta a partir dos objetos de longa duração;
  2. Evite implementar um método Finalize. O coletor de lixo deve promover objetos propensos ao Finalize para gerações anteriores para facilitar a finalização, que os faz objetos de vida longa;
  3. Evite ter objetos propensos ao Finalize fazendo referência a qualquer coisa. Isto pode fazer com que o objeto referenciado se torne de vida longa.
  • Definir variáveis de membros desnecessários como null antes de fazer chamadas de longa execução - Antes de você bloquear uma chamada de longa execução, você deve definir explicitamente todas as variáveis de membros desnecessários como null antes de fazer a chamada, para que eles possam ser reunidos. Isto é demonstrado no fragmento de código seguinte:
class MyClass{

 

private string str1;

 

private string str2;

 

 

 

void DoSomeProcessing(?){

 

str1= GetResult(?);

 

str2= GetOtherResult(?);

 

}

 

void MakeDBCall(?){

 

PrepareForDBCall(str1,str2);

 

str1=null;

 

str2=null;

 

// Make a database (long running) call

 

}

 

}

Isso se aplica a todos os objetos que ainda estão estaticamente ou lexicalmente acessíveis, mas não são realmente necessários:

  • Se você não precisar de mais uma variável estática em sua classe, ou alguma outra classe, configure-a como null;
  • Se for possível, suprima o seu estado, esta também é uma boa ideia. Você pode ser capaz de eliminar mais de uma árvore antes de uma chamada de longa execução, por exemplo;
  • Se houver quaisquer objetos que possam ser eliminados antes da chamada de longa execução, defina-os como null.

Não defina variáveis locais para null (C#) ou Nothing (Visual Basic NET.), pois o compilador JIT pode determinar estaticamente que a variável não é mais referenciada e não há necessidade de definí-la explicitamente como null.

  • Minimizar alocações ocultas - A alocação de memória é extremamente rápida, pois envolve apenas uma relocação de ponteiros para criar espaço para o novo objeto. No entanto, a memória precisa ser coletada em algum momento e isso pode prejudicar o desempenho, portanto fique atento às linhas de códigos aparentemente simples, que na verdade resultam em várias alocações.

     

    Preste atenção também na alocações que ocorrem dentro de um loop, como as strings concatenadas usando o operador + =. Finalmente, os métodos de hashing e de comparação são, particularmente, lugares ruins para colocar alocações, pois são, muitas vezes, chamados repetidamente no contexto de pesquisa e classificação.

  • Evitar ou minimizar gráficos de objeto complexo - Tente evitar o uso de estruturas complexas de dados ou objetos que contenham uma grande quantidade de referências a outros objetos. Isso pode ser custoso para alocar e criar um trabalho adicional para o coletor de lixo. Os gráficos mais simples possuem localização superior e menos códigos são necessários para mantê-los. Um erro comum é tornar os gráficos muito gerais.
  • Evite pré-alocação e fragmentação de memória - Programadores C++ muitas vezes atribuem um bloco grande de memória (usando malloc) e, em seguida, utilizam umas partes de cada vez, para salvar múltiplas chamadas para malloc. Isso não é aconselhável para código gerenciado por várias razões:
  1. Alocação de memória gerenciada é uma operação rápida e o coletor de lixo foi aperfeiçoado para alocações extremamente rápidas. A principal razão para a pré-alocação de memória em código não gerenciado é o de acelerar o processo de alocação. Isto não é um problema para o código gerenciado;
  2. Pré-alocando a memória, você faz mais alocações do que necessário o que pode desencadear as coletas de lixo desnecessárias;
  3. O coletor de lixo é incapaz de recuperar a memória que você recicla manualmente;
  4. Memória pré-alocada envelhece e é mais para difícil para reciclar quando é finalmente liberada.

O coletor de lixo oferece mais dois métodos adicionais: Finalize e Dispose. Vamos discutir as diretrizes para esses métodos no próximo artigo desta série.

***

O texto original está disponível em: http://blog.monitis.com/index.php/2012/04/10/improving-net-application-performance-part-4-garbage-collection/

 

 

http://imasters.com.br/artigo/24621/dotnet/melhorando-o-desempenho-do-aplicativo-net-parte-04

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.