C#  -  Algumas dicas para melhorar o desempenho


 Hoje veremos mais dicas para melhorar o desempenho do seu código C#.

Se você deseja usar C# em aplicativos de desempenho crítico, as seguintes frases também devem entrar em sua mente: desempenho, velocidade e uso de memória.

Para te ajudar neste objetivo a seguir temos algumas dicas:

1. Evite o Coletor de Lixo ou Garbage Collector

Em uma aplicação .NET, memória e desempenho estão muito ligados. O mau gerenciamento de memória pode prejudicar o desempenho de várias maneiras. Um desses efeitos é chamado GC Pressure ou Memory Pressure.

A pressão do GC (pressão do coletor de lixo) é quando o GC não acompanha as desalocações de memória. Quando o GC é pressionado, ele passará mais tempo coletando lixo, e essas coletas virão com mais frequência. Quando seu aplicativo gasta mais tempo coletando lixo, ele gasta menos tempo executando código, prejudicando diretamente o desempenho.

Evitar a coleta de lixo ajuda a manter altos níveis de desempenho, além de otimizar o uso de memória.

Para isso você pode por exemplo definir a capacidade inicial para coleções dinâmicas,  usar Structs ou Records ao invés de classes quando isso for possível, usar StringBuilder com parcimônia, evitar o uso de Finalizers, usar ArrayPool para arrays grandes, etc.

2. Utilizar StackAllocators

Se seu aplicativo estiver fazendo muita alocação de memória, você poderá obter melhor desempenho evitando o custo de liberar e alocar memória por conta própria.

Você pode conseguir isso movendo para cima e para baixo em sua pilha até encontrar um bloco não utilizado e, em seguida, marcá-lo para reutilização. Um alocador baseado em pilha faz exatamente isso. Ele aloca memória de uma região chamada pilha sobre a qual novos blocos são colocados quando blocos liberados são encontrados.

Os alocadores de pilha podem ser mais eficientes do que os alocadores de heap, pois não precisam realizar nenhuma sincronização ou alocação de heap. Isso pode ser especialmente importante para aplicativos que precisam ser altamente eficientes, como jogos ou aplicativos em tempo real.

A palavra-chave StackAlloc em C# permite alocação e desalocação muito rápidas de memória não gerenciada. Ou seja, as classes não funcionarão, mas os primitivos, structs e arrays são suportados.

No geral, os alocadores de pilha são uma boa escolha para aplicativos que precisam ser altamente eficientes e que precisam de um alto grau de controle sobre o gerenciamento de memória. No entanto, eles não são adequados para todos os aplicativos e é importante considerar as desvantagens antes de usá-los.

3. Não reutilize referências de objetos

Este é um erro muito comum entre programadores iniciantes em C# : em vez de limpar um objeto e liberar sua memória quando não for mais necessário, eles simplesmente continuam usando até que não haja mais memória disponível.

Isso pode ser especialmente perigoso em aplicativos de servidor em que muitos programas compartilham um servidor; a referência de objeto vazada de um programa pode derrubar todos os outros programas que usam esse servidor.

Para evitar a criação de referências vazadas, defina explicitamente seus objetos como null quando terminar com eles — ou certifique-se de criar apenas quantas referências forem necessárias.

Por exemplo, o seguinte código criará um vazamento de memória:
 
Object object1 = new Object();
Object object2 = object1;

// object1 e object2 agora se referem ao mesmo objeto
object1 = null;

// object1 não é mais usado mas não será desalocado
// porque o object2 ainda faz referência a ele

Para evitar vazamentos de memória, é importante nunca reutilizar referências de objetos. Em vez disso, você deve criar novas referências para objetos sempre que precisar usá-los.

Aqui está um exemplo de como criar uma nova referência para um objeto:

// Este código não vai criar uma falha na memória

Object object1 = new Object();
Object object2 = new Object();

// object1 e object2 agora se referem a objetos diferentes

Ao seguir essas diretrizes, você pode ajudar a garantir que seu código não tenha vazamentos de memória.

4. Evite o mapeamento de memória (para arquivos/sistemas de arquivos grandes)

Uma armadilha ao usar os métodos File.ReadAllText() ou File.ReadAllLines() do C# é que eles carregam todo o conteúdo na memória imediatamente. Isso pode causar gargalos de desempenho se você estiver lidando com arquivos muito grandes e/ou subsistemas de armazenamento lentos, como discos rígidos.

Uma maneira de contornar isso é usar um arquivo mapeado na memória, que cria espaço virtual na memória que se parece com um arquivo em disco.

Aqui estão algumas alternativas ao mapeamento de memória para arquivos ou sistemas de arquivos grandes:

5. Evite serialização/desserialização desnecessária

A plataforma  .NET fornece recursos de serialização e desserialização de objetos prontos para uso. O mesmo vale para várias linguagens .NET principais, como C#, F# e Visual Basic .NET. No entanto, em determinadas circunstâncias, essas funções podem causar problemas de desempenho significativos em seu aplicativo.

Isso é particularmente verdadeiro quando uma quantidade não trivial de dados precisa ser serializada ou desserializada.

Se você estiver usando esses recursos regularmente, é importante entender como eles funcionam para que você possa selecionar uma estratégia apropriada para processar seus dados.

Dê preferência para usar a classe System.Text.Json para realizar essas operações, esta nova biblioteca esta integrada ao framework e elimina a a necessidade de dependências externas para lidar com JSON. É uma biblioteca leve que se concentra em funcionalidade e desempenho simples.

6. Mantenha o JIT afastado dos métodos não utilizados

O compilador Just-In-Time (JIT) é um processo que lê o CIL e o traduz em código nativo. Ao construir um aplicativo, você não quer que o JIT compile todas as suas classes — principalmente se elas forem usadas apenas uma ou várias vezes na inicialização. Ao compilar apenas o que é necessário quando necessário, você pode fazer seu aplicativo iniciar muito mais rápido!

Existem várias maneiras de garantir que sua inicialização seja mais rápida e uma delas é usando Reflection : Remova métodos não utilizados desses objetos usando System.Reflection.Emit  que
contém classes que permitem a um compilador ou uma ferramenta emitir metadadosMSIL (Microsoft Intermediate Language) e se desejar gerar um arquivo PE no disco.

7. Faça medições no modo de produção em vez do modo Debug

Em muitas linguagens de programação, os aplicativos são executados em um modo de depuração especial que permite aos desenvolvedores fazer coisas como definir pontos de interrupção e imprimir mensagens de depuração. Essa é uma ótima maneira de os desenvolvedores testarem seu código enquanto o desenvolvem — e durante o desenvolvimento é essencial. Mas depois de terminar a codificação, você deve voltar ao modo de produção antes que seu aplicativo seja liberado.

A execução do seu aplicativo no modo de produção elimina aquelas linhas extras de código que são essenciais apenas durante o desenvolvimento.

8. Utilize um ofuscador de código

Outra última maneira para otimizar melhor sua aplicação .NET é usar um .NET Obfuscator.

Isso ajudará você a otimizar fortemente seu aplicativo e obterá um aplicativo protegido, evitando descompilação e engenharia reversa.

A seguir vou deixar uma lista de alguns ofuscadores de códigos:

Para obter uma relação maior e com mais detalhes veja o link : https://github.com/NotPrab/.NET-Obfuscator

E estamos conversados.

"Ainda que eu fale as línguas dos homens e dos anjos, se não tiver amor, serei como o sino que ressoa ou como o prato que retine."
I Coríntios 13:1

Referências:


José Carlos Macoratti