Sobre otimização em aplicações .Net – Parte 1

Bom, acredito que nunca tenha postado algo específico do meu trabalho.

Um dos motivos para isso seria que parte das coisas que faço são muito sensíveis para compartilhar.

Mas esse final de semana comecei um projeto do zero para identificar e otimizar o Kenshi Mod Manager (KMM) do jogo Kenshi, porque isso vai me deixar exercitar as tarefas que preciso em meu trabalho e o risco é mais controlado.

Resumidamente o gerenciador tem vários recursos, mas quando desenvolvi há cerca de um ano, haviam problemas sérios de performance, bom… não pro meu pc, mas pra quem utiliza.

Tenho cerca de 300+ mods, e indexar os conflitos nele causam ~1GBs e um tempo considerável pra apresentar as falhas

Como que comecei a análise

O KMM é um WPF em .Net Core 3.1 e eu acho realmente muito trabalhoso analisar cada parte do código sem ter uma ferramenta externa, já que o projeto já passou de 5K de linhas..

Então já que eu precisava estudar gRPC iniciei primeiro criando dois projetos, um server e um client.

sendo que o server está no KMM e o client em um console no MMDHelpers.

A alteração no KMM foi muito simples, bom … já que ele não era uma aplicação web, eu apenas subi o kestrel.

Uma observação, neste webserver que eu estou subindo, estou ignorando a parte de segurança completamente, já que irei identificar os problemas, remover o pacote e disponibilizar a nova versão.

Com o Client eu pude enviar comando para iniciar ou terminar as métricas no KMM

https://gist.github.com/millerscout/e9228ffe5f61edf9f151cab3aa61dbf7
Simples não?

o resultado foi este:

Eu consigo observar quantas coletas em Gen0-2, a quantidade de memória e o tempo que levou.

o tempo aqui não é meu foco ainda, mais para abaixo explico uma otimização para tempo (mas é tiro de canhão pra matar mosca)

Primeiro Passo

Particularmente eu tento quebrar os problemas mais complexos para o mais simples possível, e existe um item que não tinha nenhuma coleta no GC mas era o primeiro item que eu queria otimizar

Temos aqui o cenário:

https://gist.github.com/millerscout/a628f32fb4edd9d710c0fbb6c2b775ff

O código nem causa problema, afinal só estou usando o IO.Path.GetExtension e comparando se o arquivo está naquela extensão.

então tinha duas opções:

  • Usar String.EndsWith
  • Criar o meu EndsWith

Ainda estou encontrando formas de pegar o código base do c# pra remover alguns recursos que não preciso validar, ganhando tempo de processamento.

Em minha solução eu achei mais fácil iterar uma string do final pro começo, levando em conta que se qualquer char for diferente ele retorna false,

o código ficou desse jeito:

https://gist.github.com/millerscout/879956f6a0038330992827d09fedd429

A única coisa que faltou foi um benchmark certo?

então tá ai:

https://gist.github.com/millerscout/52f6e56a64faa3658e7e6958c1261b29

não apenas descartei o EndsWith de string, como percebi que minha versão é mais eficiente pro problema um ganho de 69% de performance, se este problema estivesse em um cenário de 4M de iterações o ganho seria de 195.16ms para 60.44ms, Bom depende muito do cenário né? mas… é algo á se pensar quando for otimizar, aqui nem usei o Princípio de Pareto, só foi pra brincar mesmo.


Pontos de atenção:
no meu cenário não preciso verificar se a string é case-sensitive ou possui acentos.

Segundo Passo


Fiz uma dúzia de alterações simples:

  • Parei de usar List para um array pequeno onde eu não precisava fazer nada apenas conter a lista.
  • Alterei algumas classes para structs pois as classes eram imutáveis e também não há risco de cópia em memória delas.
  • adicionei o capacity em algumas até o momento onde revisei

O que seria dar essa lista sem o commit não é mesmo? segue ai:

https://github.com/millerscout/Kenshi-Mod-Manager/commit/7f9ef386bdcb902ed3a168afac8eaaa23babf4c1

o resultado foi bem promissor:

Gen 0: 990 redução de 18% comparando com 1217 anteriormente
Gen 1: 284 redução de 31% comparando com 351 anteriormente
Gen 3: 28 aumento de 16% comparando com 24 anteriormente

Primeira parte encerrada aqui.

Você pode continuar lendo a Parte 2

Conclusão

Ainda há um longo caminho de aprendizado e técnicas para aprimorar minha capacidade de fine-tuning em .Net, mas acredito que o resultado mostra que não é rocket science, apenas precisamos nos policiar em coisas simples para trazer um ganho maior.

Créditos

Acredito que essa minha vontade de otimização não seriam possíveis caso as conversas com o @crocco @Massato não tivessem acontecido.

Eu tive também alguns materiais para me inspirar e aprender, bem como algumas referências, sendo elas:

o tio Scott, Elemar Jr. e Konrad Kokosa

Estive consumindo vários artigos da Eximia.Co, documentações da própria microsoft em momentos específicos os seguintes itens:

Sneak Peek

Próximo post gostaria de experimentar mais sobre sobre warmup em .Net, comecei ler algumas coisas, assim que tiver algo conclusivo trago para compartilhar 🙂

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *