Leben Dia 39 e 40

Mono é um respiro novo pra algo antigo :sweat_smile:

Depois que fazer um refactor das coisas que tinha (desde maio de 2020) e enviar pro mono

achei até que seria complicado fazer toda a alteração, mas não… como eu já tinha separado o Leben.Core
a transição foi bem simples.

a única dificuldade foi re-lembrar como fazer Draw no mono, como não é uma Engine tive que começar do zero algumas coisas.

no dia 39

só foi o refactor, fazer draw de object, alguns itens de interface,
viewport draw, um relógio pra esboçar um comportamento, estações e recursos, provávelmente teve outras coisas, mas eu acabei esquecendo.

hoje foi implementar as animações, caching de texturas e como eu quero que exista uma possibilidade de modificar o jogo nas texturas também.

eu simplifiquei o jogo para carregar PNGs, não é o ideal em alguns dispositivos haverá perda de performance, mas eu penso que por enquanto não é um problema.

também implementei um sprite tipo atlas que tinha no godot (eu gostei disso, daquele engine)


consigo configurar todas texturas num arquivo Yaml:

gameObjects:
  - texturePath: house.png
    textureName: house
    size: { x: 50, y: 50 }
    scale: 1
  - textureName: "grassTexture"
    texturePath: "Content/grass.png"
    scale: 1.0
    size: { x: 48, y: 48 }
    offset: { x: 1, y: 0 }
    isAnimated: true

a melhor parte nisso é a customização externa, ainda vou deixar uma forma de apertar um botão e as texturas serem carregadas de novo, assim posso simplesmente ir montando com placeholders e atualizando conforme vou achando que funciona.

o jeito que implementei é muito tranquilo ir de um objeto sem animação alguma pra algo que se move.
então posso deixar tudo com um única célula e assim que for conveniente apenas mudo o png e atualizo o yaml

um vídeo pra mostrar como ficou …

era só, nada rocket science.

Esteja Curioso

Leben dia 38

Voltei pro desenvolvimento do Leben…

Vai ano, volta ano e sempre pego esse projeto, toco por alguns dias e paro.

Hoje eu cansei de usar o godot, estava na versão v3.3.2.

estive fazendo o upgrade do projeto pra versão v4.2.2, mudou bastante coisa, acabou que eu tive que portar o Leben.Core pra net core 6.0

terminei de portar e comecei trabalhar no projeto
Vira e mexe vem o erro:

eu deleto o script, mudo o script, crio um novo, mas nunca é uma coisa simples, parece que foi uma gambiarra pra funcionar C# nessa engine.

por conta disso vou mudar de engine de novo…
pra quem não viu o histórico…

eu comecei o projeto com Unity, foi pro Mono, Unreal, tentei zenko/stride.

Voltarei pro Mono que já tem um ecossistema mais próximo do que quero trabalhar

eu estou tentando ir desenvolvendo com BDD então eu não preciso de uma Engine ainda

só quero adicionar os objetos programaticamente e rodar pelo Leben.Core.

estou indo por essa abordagem porque quero ter a possibilidade de ter Full mod support, sendo possível alguém se quiser re-escrever como a IA funciona.

mas essas engines são muito travadas…

era isso

Esteja Curioso!

docker-compose.yml com podman

Contexto

Mesmo adorando usar docker, principalmente porque ele tem uma interface :heart_eyes:

acabei tendo que buscar alternativas, já que ele começou ser pago para empresas, sempre tentei compartilhar a liberdade que existe em usar um ambiente dentro de um container.

Pra mim é algo mágico poder simplesmente matar o container e fazer de novo, sem medo de perder algo, ou até mesmo pedir pra alguém testar algo.

enfim…

um dos meus projetos é apresentar alguns environments para outras pessoas, afinal é mais prático você apresentar algo á alguem usando o recurso do que pedindo pra pessoa estudar

em minhas conversas sempre é o mesmo papo, pergunto se a pessoa conhece docker, e no final a pessoa geralmente fala “já ouvi falar, mas nunca usei”, quando isso acontece eu tenho a oportunidade mais divertida, porque hoje eu compartilho minha tela e começo mostrando como é “dificil” subir alguma coisa, eu escolho o item mais simples que conheço: rabbitmq que por um acaso é um outro amor que tenho 😀

Exemplo de subir uma imagem

compartilho geralmente meu repositório de environments

então abro um console e digito:

git clone https://github.com/millerscout/environments.git
cd environments
cd rabbit
cd Default
docker-compose up


e Voilá! tá pronto, só abrir o localhost:15672

logo com user guest e senha guest e pronto

é tão simples que acaba sendo mais fácil ainda quando eu mostro como eu fiz, no docker-compose.yml

não é nada mais nada menos que só isso:

version: '3'

services:
  rabbitmq:
    image: "rabbitmq:3-management"
    hostname: "rabbit"
    ports:
      - "15672:15672"
      - "5672:5672"
    labels:
      NAME: "rabbitmq"

configurando o podman

bom primeiro eu tive que instalar o podman (duh?!)

tive que instalar um script em python para funcionar o podman-compose, tudo instalado, tranquilamente

mas quando executei: podman-compose up

sem sucesso acabei fazendo o que todo dev experiente faz, abri o google e pesquisei :scream:

e achei dois posts que são importantes:
https://github.com/containers/podman/issues/11530 que é o “problema” que eu tenho
https://www.redhat.com/sysadmin/container-image-short-names que é a explicação do motivo que acontece.

então acabei alterando meu yml pra funcionar tanto com podman quando docker apenas acrescentando a origem da imagem

version: '3'

services:
  rabbitmq:
    image: "docker.io/library/rabbitmq:3-management"
    hostname: "rabbit"
    ports:
      - "15672:15672"
      - "5672:5672"
    labels:
      NAME: "rabbitmq"

sim… somente essa mudança.

bom, agora que você sabe como rodar isso tanto em docker quanto em podman, vai lá e começa brincar e compartilhar com alguém do seu time que ainda não está usando 😀

até mais

Esteja Curioso!

Leben – Day 37

Continuando com o desenvolvimento

fiquei vários dias olhando pra tela sem vontade de escrever nada, fazer o desenho do mapa, estava me deixando maluco, o perlin não estava funcionando como eu queria, até que joguei Swag and Sorcery, acabei me lembrando de como eu gosto de 2D side-scrolling

então vou me forçar simplificar mais o Leben pra algum dia terminar ele :sweat_smile:

O que fiz de novo?

estou começando desenhar alguns tilesets, havia falado que ia usar Krita, mas acabei preferindo aseprite, comprei no steam mesmo, se não me engano usei o grátis e me apaixonei.

nada muito completo, apenas o background e o chão, vou fazer o render do céu de outra forma pra facilitar transição de dia/noite, mas por enquanto, é o suficiente.

ainda tem muito trabalho, mas eu estou gostando:

estive sincronizando com a cloud os assets e os códigos que estive fazendo.

vou montar agora algumas construções e começar “brincar” com o jogo

Leben – Day 36

Voltando ao projeto

Estive trabalhando no meu “joguinho”

ainda há muito trabalho, aproveitei minhas férias pra descansar e agora que estou um pouco menos estressado e consegui um cadin de energia

acabei voltando pro Leben.

O que fiz de novo?

acabei implementando o backbone de estruturas no mapa, algo simples onde toda entidade do mundo tenha algum tipo de input e output, por enquanto pensei pequeno com um poço, como havia citado no Leben – 25-2, eu estou implementando tudo via BDD, quero continuar neste formato

ainda não tem tantos detalhes, primeiro estou criando um poço, então indicando que há um ator interagindo com o poço e forço a conclusão da tarefa do ator, então uma referência de “balde de água” é adicionada no inventário do poço.
mas está funcional

eu gosto muito de usar o BDDFy

somente por isso:

futuramente caso necessário eu terei uma comportamento esperado em vários pontos do jogo, evitando que eu quebre :sweat_smile: alguma coisa.

Falando em quebrar…

Estava todo feliz pensando que iria usar a configuração de noise do mapa que havia feito e começar implementar as entidades no mapa.

O resultado é que agora todos os pontos que eu separei nos biomas (desert, mountain, deepforest, forest, beach, nearwater, deepSea, nearwater, beach) estavam quebrados, a única mudança foi que troquei de PC, provávelmente.

mas não tem problema, eu já queria criar um sistema separado pra gerar mapas e poder “pintar” um mapa.
na minha cabeça irei usar a técnica de geração de mapa procedural para fazer maior parte do trabalho, indicando biomas, setando onde é possível ter determinado tipo de recurso e tal, depois dele gerar o mapa eu dô os “tapas” para fazer a finalização.

Em breve mostro uma ferramenta que acabei dazendo para visualizar o mapa de maneira rápida.

ate mais 😁

Async não é paralelismo e não… Async não é mesma coisa que sync.- Parte 1

Intro

Calma, leia até o final :sweat_smile: juro que fará sentido o título, bom… pra mim fez…..

Não sei o quanto vocês conhecem de Async e a famosa maquina de estado no contexto de C#

particularmente eu já tive vários problemas e cheguei odiar porque sempre tive problema e sempre culpei o compilador….

bom, depois de criar vergonha na cara e meter a fuça em como as coisas funcionam aprendi um pouquinho, o suficiente pra não cair em algumas armadilhas.

tenho alguns Links de referência como meu book of reference, tudo que achei útil quando tive problemas eu acabei adicionando nele pra estudar aos poucos.

A importância de saber sobre async e sua máquina de estado.

Recentemente fui convidado pra fazer um Code Review em um código, particularmente devido o projeto que toco é complicado fazer code-review e quando acontece é algo bem pontual onde preciso separar na agenda.

e encontrei alguns itens que me chamaram atenção, foi removido paralelismo, remoção de async e foi adicionado um tal de GC.Collect().

Não me entenda mal, mas se você faz isso, talvez seja importante ler o livro: Pro .Net Memory Management: For Better Code, Performance, and Scalability by Kokosa ele dá um background ótimo sobre com funciona GC e convenhamos… A linguagem já tem mais de 20 anos, com engenheiros super competentes que pensaram durante anos da forma mais eficiênte deste processo, vamos ter humildade :sweat_smile:, existem pouquíssimos casos que você realmente precisa fazer isso, pelo que me recordo em debugging ou fazendo benchmark, com excessão desses pontos, você vai apenas piorar a heurística e se ele está sendo o problema… novamente, leia o livro do Kokosa e comece usar o Stack.

bom pulando pra parte que eu queria falar :sleeping:

Async at last!

não sei se vocês tem o costume de ler o que o código vai gerar ao escrever o código, não é sempre que você precisa disso, na verdade você ignora essa parte porque são poucos cenários que você precisará entender o que acontece por trás do código gerado ou IL pra determinar uma nova forma de escrever código

um código simples :

using System;
using System.Threading.Tasks;
public class TestSubject {
    public void DoSomething() {
    }
    public async Task DoSomethingAsync() {
    }
}

e o compilador irá traduzir isso para:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class TestSubject
{
    [CompilerGenerated]
    private sealed class <DoSomethingAsync>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        public TestSubject <>4__this;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    public void DoSomething()
    {
    }

    [AsyncStateMachine(typeof(<DoSomethingAsync>d__1))]
    [DebuggerStepThrough]
    public Task DoSomethingAsync()
    {
        <DoSomethingAsync>d__1 stateMachine = new <DoSomethingAsync>d__1();
        stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
        stateMachine.<>4__this = this;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
}

é possível perceber que toda vez que você deixa uma assinatura de “async” no método ele gera uma máquina de estado, é até um dos motivos que eu tento não criar nested async, afinal invariávelmente será alocado em memória, então em cenários críticos eu penso bem… Continuando….

Precisava dar esse contexto caso alguém não tivesse ainda a oportunidade de saber o que acontece por baixo dos panos, então foi um resumo pra continuar

em minha memória do código de algum tempo atrás era o seguinte:

public async Task SomeMainTask(){

    var id = 0;
    var processoUm = Task.Run(()=>{ id = DoSomething();});
    var processoDois = DoOtherThing();

    await processoUm;

    (... outros processos sincronos e assíncronos)

    var processoDois = DoOtherThing(id);

    Task.WaitAll(
        processoDois,
        (...)
    );
}

public async Task<id> DoSomething(){
    (... alguma chamada pro banco)
    ///código apenas pra contexto
    return Task.FromResult(id);

}

e quando eu li o código:

public void  SomeMainTask(){

    var id = 0;
    var processoUm = DoSomething();
    var processoDois = DoOtherThing();

    (... outros processos sincronos e assíncronos)

    var processoDois = DoOtherThing(id);    
}

public id DoSomething(){
    (... alguma chamada pro banco)
    ///código apenas pra contexto
    return id;

}

eu fiquei meio intrigado, eu queria entender a motivação… particularmente eu faço tudo sincrono mesmo e eventualmente quando estou fazendo meu próprio code-review eu penso se faz ou não sentido colocar um async.

quando questionei, a resposta foi que async e sincrono seriam as mesmas coisas no final das contas ou seja :

isso

var id = DoSomething();

public int DoSomething(){
    (...chamada pro banco)
    return someid;
}

é igual isso:

var id = await DoSomething();

public Task<int> DoSomething(){
    (...chamada pro banco)
    using(var someconn = new SqlConnection(connectionstring)){
        return someconn.FirstOrDefaultAsync<int>("select 1");   
    }   
}

será?

tentei explicar que não era… afinal na própria documentação da microsoft (inclusive… que maravilha de doc ) explica exatamente o que eu disse. Apesar do meu esforço de mostrar que ele vai bloquear uma thread até ter resposta…

achei mais simples criar um código muito simples pra apresentar o problema:

Apenas pra dar contexto, eu sou um usuário assíduo do LinqPad, logo quando estiver escrito “someText”.Dump(); no código abaixo, entenda como Console.WriteLine(“sometext”);

async System.Threading.Tasks.Task Main()
{
    int nWorkers; 
    int nCompletions; 

    ///aqui estou forçando que apenas exista uma única Thread no meu sistema pra apresentar um cenário que dá pra observar o comportamento
    ThreadPool.SetMinThreads(1, 1).Dump();
    ThreadPool.SetMaxThreads(1, 1).Dump();

    /// apenas me assegurando que é apenas uma thread..... vai que... 
    ThreadPool.GetMaxThreads(out nWorkers, out nCompletions);
        "workers: {nWorkers}".Dump();"nCompletions: {nWorkers}".Dump();

    ///estou deixando uma thread executando sem parar, com um intervalo de 2 segundos, como se fosse um heart-beat.
    System.Threading.Tasks.Task.Run(async ()=>{
                while(true){
                    "Heartbeat".Dump();
                     await System.Threading.Tasks.Task.Delay(2000);
                }
    });


    for(var i = 0; i<=4; i++){
        /// aqui estou criando algumas Tasks assíncronas para executar alguma ação representando um insert no banco.
        await System.Threading.Tasks.Task.Run(async ()=>{
                await System.Threading.Tasks.Task.Delay(5000);"{Thread.CurrentThread.ManagedThreadId}-{DateTime.Now.ToString("HH:MM:ss")}".Dump();
        });
    }

    "-----------------------Async Done-----------------------".Dump();
    for(var i = 0; i<=4; i++){
        /// aqui estou criando as mesmas Tasks mas desta vez estou forçando a execução síncrona para executar a mesma ação no banco.
            System.Threading.Tasks.Task.Run(()=>{
                System.Threading.Tasks.Task.Delay(5000).Wait();
                $"{i} - {Thread.CurrentThread.ManagedThreadId}-{DateTime.Now.ToString("HH:MM:ss")}".Dump();
            });
    }
}

O que você acha que aconteceu?

eu tive que gravar um vídeo…:sweat_smile:

como esperado a thread que está resposável pelo heart-beat continua a execução, é liberada e então a task Async de “persistência no banco” consegue trabalhar, termina e assim o fluxo continua..

agora quando chega no síncrono… nenhum funciona, infelizmente não consegui compreender o motivo, a thread acabou ficando em lock no Task.Delay, mas isso eu não vou tentar entender hoje.

mas espero que esse post traga a luz para alguém que assim como eu também já sofreu com async.

p.s. eu sei que acontecer um thread starvation em alguns cenários é difícil dependendo do seu cenário, mas no cenário que eu verifiquei isso pode ser a diferênça entre aumentar a capacidade do servidor pra dar vazão no processamento, sendo que o custo pode ser evitado com um uso inteligênte dos recursos.

p.s² Pegadinha! eu não falei sobre paralelismo ainda :laughing:, mas o que eu falaria não escapa da gloriosa doc da mãecrosoft:

e depois continuo :p

Esteja Curioso!

Otimizando o KMM – Parte 2

Onde paramos mesmo?

Em nosso último Snapshot tivemos esse resultado

nosso TodoList é pra tentar diminuir o tempo e agora carregar os dados on-demand.

O commit até o momento Commit
Removi algumas propriedades e processamento inútil, o snapshot ficou:

Ainda não descobri o motivo que deu ~12segs de diferença quando eu removi as coleções inúteis, sei que melhorou o gen0 e gen 1, não vou focar nisso agora, porque estive fazendo vários experimentos, fazendo buffering a asyncwrite mas acabou dando na média de 60seg.

Voltarei no tempo de leitura, quando estiver refatorando a leitura de arquivo especificamente

Encontrei um novo problema.

Apesar de ter indexado todos os valores e poder acessar rapidamente, conforme o print.

Ainda tenho o desafio de ter que pesquisar no sqlite a informação com o Tipo, Key e section
o que se resume em :

select * from modchange mc
join (
    select Key, Type,Section from mod m
    join modchange mc on mc.modId = m.id
    where hash = 'HPrv0uf3qAfqvypEDYYHCgC8EQQsmLoBGGNhjOYS5nM='
) mcd on mcd.key = mc.key and mcd.Type = mc.type and mcd.section = mc.section

Porém ao executar temos 17Segs de execução.

o que acaba sendo inviável.

Uma solução que pensei foi transformar key,type e section em um identificador único, e então usar como chave, tornando o processo muito mais rápido na pesquisa.

NOTA: enquanto estava fazendo o hash das 3 string, o tempo foi pra 2 minutos!,

então removi o Type, já que ele já é um Enum, não precisa participar da criação do hash

Eis o resultado em disco e tempo:

O tempo médio pra processamento está em cerca de 1 minuto e 130MB salvo em disco, mas eu acho que está ruim, apesar de ser a primeira execução, eu quero que fique em torno de 30 segundos.

São aproximadamente 1kk de alterações. mais ou menos 43k de changes por segundo.

Depois de descansar, voltei ao problema.

Fiquei um tempo tentando usar hash, tirando daqui, mudando ali pra ter um tempo menor… então me lembrei que eu tinha esquecido de fazer algo BEM mais simples que resolveria o problema, eis o código em sql:




CREATE INDEX idx_mod_change ON ModChange (Key,Type,Section);
o resultado, como esperado: 

pois é, as vezes você está tão focado em um cenário que acaba deixando passar os detalhes mais simples, no meu caso eu parei pra descansar, assistir filme e tirar o dia pra desansar, voltei e imitei o panda ao perceber o que tinha deixado pra trás.

era isso por hoje.

Esteja Curioso!

Otimizando o KMM – Parte 1

Onde paramos mesmo?

Em nosso último Snapshot tivemos esse resultado

Gen 0: 187
Gen 1: 72 
Gen 3: 11 
Tempo: 13179ms 
Memória: 590mb 

Meu objetivo agora era parar de ficar perdendo tempo re-processando todos os mods, não fazia sentido.

Há várias soluções que podemos aplicar, a mais simples “pra mim”, seria utilizar um Helper que fiz pra SqlLite sendo assim eu iria facilitar e muito a quantidade de código que eu escreveria.

Primeiro problema

Fui percebendo que estava fazendo uma iteração desnecessária, pois eu já estava carregando os dados do Mod na memória, depois de terminar de ler tudo eu iterava de novo só pra identificar as modificações… mas isso é redundante e o resultado era apenas ter o valor “Old”.

Porém como os mods podem ser re-ordenados o “Old” se mudará conforme o contexto, logo… é uma informação inútil.

Resultado: Remoção desses código inúteis.

Segundo problema

Eu estava usando minha Library então, o passo mais simples seria abrir uma connection com o banco e ir salvando no DB, algo como:

    var db  = new DataService();
    db.Insert(query, parametros);

sim… é só isso

o único problema é que não estou inserindo 10 itens, e sim mais de 500K, então meio que ele ficou processando por mais de 1 hora e não havia terminado (eu dei uma pausa pra descansar, ele ficou processando enquanto isso) logo precisava efetuar otimizações… (que novidade :x)

Mas eu já esperava isso, eu havia encontrado uma forma eficiente em SqlLite que itera e insere os dados, um dia eu posto mais detalhes (talvez)

o código no final ficou:

        static Dictionary[] List = new Dictionary[5000];
        static int lastIndex = 0;
        public static void AddToList(int id, ItemType type, string key, string name, string empty, string flag2, State state)
        {

            List[lastIndex] = new Dictionary(7)
            {   { "ModId", id },
                {"Type" , type },
                {"Section" , key },
                {"Key" , name },
                {"OldVal" , string.Empty },
                {"NewVal" , flag2 },
                {"State" , state },
            };
            lastIndex++;
            if (lastIndex > 5000)
            {
                var db = new DataService();

                   db.InsertBatch(@"INSERT INTO ModChange (
                                            ModId,
                                            Type,
                                            Section,
                                            [Key],
                                            OldVal,
                                            NewVal,
                                            State
                                        )
                                        VALUES (
                                            :ModId,
                                            :Type,
                                            :Section,
                                            :Key,
                                            :OldVal,
                                            :NewVal,
                                            :State
                                        )", List.Take(lastIndex));
            List = new Dictionary[5000];
            lastIndex = 0;


            }
        }
Bom um pouquinho grande, mas dá pra entender, eu coloquei cada batch pra 5k de registro, me parecia ser suficiente, mas vamos ver como ficou o resultado no GC?


É… mais uma vez aumentamos a quantidade de varreduras do GC, mas o que podemos fazer pra resolver isso?

Nós estamos criando uma lista de dictionary, que depois será criado transformado em uma lista de parameters conforme o código aqui

vamos resolver isso criando uma lista de parameters.

  static SQLiteParameter[][] List = new SQLiteParameter[12000][];
        static int lastIndex = 0;
        public static void AddToList(int id, ItemType type, string key, string name, string empty, string flag2, State state)
        {
            if (lastIndex == 12000)
            {
                UpdateDatabase();
            }


            List[lastIndex] = new SQLiteParameter[7] {
                new SQLiteParameter("ModId", id),
                    new SQLiteParameter("Type", type),
                    new SQLiteParameter("Section", key),
                    new SQLiteParameter("Key", name),
                    new SQLiteParameter("OldVal", empty),
                    new SQLiteParameter("NewVal", flag2),
                    new SQLiteParameter("State", state)
            };

            lastIndex++;



        }

        public static void UpdateDatabase()
        {
            var db = new DataService();
            var query = @"(..query..)";

            using (var connection = new SQLiteConnection($"Data Source={DataService.DbLocation}; Version=3;"))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                using (var command = connection.CreateCommand())
                {
                    command.Prepare();
                    command.CommandText = query;

                    foreach (var item in List.Take(lastIndex))
                    {
                        command.Parameters.AddRange(item);
                        command.ExecuteNonQuery();
                    }

                    transaction.Commit();
                }
            }

            lastIndex = 0;
        }

resultado:

o resultado pra mim é simbólicamente o mesmo, mas por que será que não houve melhora?

existem duas coisas que pioram a vida:
– GC sendo executado com frequência porque tem objeto que não está sendo usado.
– Alocar algo na memória tem seu custo..

mas e se não criarmos mais objetos que precisamos usar?

bom, fiz a adequação no código

static SQLiteParameter[][] List = new SQLiteParameter[5000][];
        static int lastIndex = 0;
        public static void AddToList(int id, ItemType type, string key, string name, string empty, string flag2, State state)
        {
            if (lastIndex == 5000)
            {
                UpdateDatabase();
            }


            if (List[lastIndex] == null)
            {
                List[lastIndex] = new SQLiteParameter[7] {
                new SQLiteParameter("ModId", id),
                    new SQLiteParameter("Type", type),
                    new SQLiteParameter("Section", key),
                    new SQLiteParameter("Key", name),
                    new SQLiteParameter("OldVal", empty),
                    new SQLiteParameter("NewVal", flag2),
                    new SQLiteParameter("State", state)
                };
            }
            else
            {
                List[lastIndex][0].Value = id;
                List[lastIndex][1].Value = type;
                List[lastIndex][2].Value = key;
                List[lastIndex][3].Value = name;
                List[lastIndex][4].Value = empty;
                List[lastIndex][5].Value = flag2;
                List[lastIndex][6].Value = state;
            }

            lastIndex++;



        }

        public static void UpdateDatabase()
        {
            var db = new DataService();
            var query = @"INSERT INTO ModChange (
                                            ModId,
                                            Type,
                                            Section,
                                            [Key],
                                            OldVal,
                                            NewVal,
                                            State
                                        )
                                        VALUES (
                                            :ModId,
                                            :Type,
                                            :Section,
                                            :Key,
                                            :OldVal,
                                            :NewVal,
                                            :State
                                        )";

            using (var connection = new SQLiteConnection($"Data Source={DataService.DbLocation}; Version=3;"))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                using (var command = connection.CreateCommand())
                {
                    command.Prepare();
                    command.CommandText = query;

                    foreach (var item in List.Take(lastIndex))
                    {
                        command.Parameters.AddRange(item);
                        command.ExecuteNonQuery();
                    }

                    transaction.Commit();
                }
            }

            lastIndex = 0;
        }

resultado:

Vou encerrar por aqui,
mas para nossa TODO list temos:
– Processar somente os arquivos novos.
– implementar o carregamento de conflitos no frontend on-demand
– remover mais campos que não usamos pra ter mais eficiência.
– revalidar partes do código que só são sujeira.

Bom é isso, até o próximo post

Esteja curioso!

Sobre otimização em aplicações .Net – Parte 3 (final?)

Continuando essa saga, espero que seja útil pra você jovem aprendiz que anseia por casos reais de otimização em um código aberto.

Cenário anterior:

Memory: 937mb
Gen 0:499
Gen 1:103
Gen 3:10

Enquanto estive removendo bugs do meu mod, e estudando o Livro do Konrad, Pro .NET Memory Management, se você não conhece, tente assistir a explicação em uma conferência sobre performance.

Crédito: Amazon.

Acabei exercitando nesse código do KMM, e não documentei de maneira prática as alterações, mas vou apresentá-las com a ausência do meu pensamento na hora.

Utilizando a técnica que apresentei na parte 2 fui iterando nos itens que foram apresentando maior impacto no GC e memória.
lista de alterações:

  • Remoção de uma lista de tags que não fazia mais sentido pro código.
  • Alteração de alguns setters para private afim de identificar quais classes eram passíveis de ser transformadas em structs.
  • Simplificação de algumas strings que estavam sendo concatenadas em apenas uma string (Agora estou lembrando do Crocco), mas já já eu uso a Span
  • dupliquei a parte que carrega algumas alterações dos mods, porém removi os pontos que não eram pertinentes para identificar os conflitos.
  • transformei mais algumas classes em Structs e adequei o código para inicializar os valores direto na struct.
  • simplificação para setar o valor de TripleInt
  • remoção de uma lista sem capacity que não estava sendo usada para nada, mas mesmo assim era populada.
  • alteração para setar o valor do filename em uma propriedade invés de sempre ficar chamando Path.GetFileName((…));

Bora pra parte de praxe o commit e o resultado:

O que achou?

não sei você, mas eu estou aqui:

Clap Applause GIF

sumarizando os resultados:

Gen 0: 187 redução de 84% comparando com 1217 do primeiro post
Gen 1: 72 redução de 79% comparando com 351 do primeiro post
Gen 3: 11 redução de 54% comparando com 24 do primeiro post
Tempo: 13179ms redução de 83% comparando com 77309ms do primeiro post.
Memória: 590mb redução de 37% comparando com 944 do primeiro post.

Apesar desses resultados muito bons…

Não me leve a mal, não é que acho o resultado insatisfatório, mas o problema aqui não é mais eficiência de código, apesar de ter espaço pra otimização.

Consideremos a frase de Lowell Arthur descrito em “Software Engineering: A Practitioner’s Approach” by Roger Pressman:
20 percent of the code has 80 percent of the errors. Find them; fix them!

O que quero dizer é que os 20% do código, até mais que isso foi otimizado, porém eu preciso resolver o armazenamento de informação e indexação ineficiente BY DESIGN

vamos compreender o cenário:

  • São mais de 300 mods
  • cada mod pode conter milhares de alterações.
  • usamos os arquivos base do jogo para identificar quais alterações foram feitas.
  • carregamos todos os mods, indexamos os conflitos e enviamos pra memória

Somente com esses pontos é explícito o próximo passo, precisamos indexar essas alterações quando não existirem em uma base de dados, caso exista e o arquivo foi alterado indexamos, do contrário apenas usamos o que está disponível desta forma utilizaremos o consumo de CPU de forma eficiente, já que uma vez que ele trabalhar pra processar os mods, o trabalho será recuperável.

Também ganharemos mais memória, já que poderemos então carregar os conflitos somente para o mod que for selecionado na tela.

Próximos passos

Meu processo de otimização neste cenário se mostra suficiente para o problema abordado,

continuarei trabalhando na otimização e indexação desses arquivos de forma eficiente.

acabei corrigindo o valores de ganho, porque eu acabei invertendo as imagens…. :facepalm:

continuarei documentando as decisões que fui tomando, tentarei trazer o brainstorm que posso ter tido.

Esteja curioso!

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

Bom, continuando o post anterior: Sobre Otimização em aplicações .Net – Parte 1, se você não leu, vai lá,
porque eu estou tentando ser o mais detalhista possível.

Nós paramos no seguinte cenário:

eu dei uma lida no código inteiro e não particularmente não vi nada gritante muito explícito, sendo assim, já posso começar fazer o que acho mais conveniente.

Quebrando o problema em partes.

Bom, o código é grande o suficiente para que minha eficiência seja baixa, então vamos separar em 5 pontos.

  • Carregar os mods.
  • Identificar os conflitos
  • Atualizar a lista de conflitos
  • Atualização da GUI
  • Feedback via progress Bar

Destes pontos eu preciso identificar aonde está o maior tempo

O que se resume em :

https://gist.github.com/millerscout/6f2db01d0e304f997ec3c61efdc9b5c8
Sim… um código muito grande não é mesmo? eu também acho que é necessário dividir pra conquistar.

Coletando Métricas

Primeiro passo foi separar todo o código que eu tinha em um ambiente mais controlado, afinal neste mesmo projeto outros itens utilizam o mesmo recurso de Feedback pro usuário e atualizam a GUI sem impacto, logo nosso foco é mais específico.

Não sei se costumam usar o profiler do VS, se não tem o costume, comecem, ele é mais barato que quebrar a cabeça, chutar o problema ou esperar que mágicamente o problema suma.

Eu tenho uma lista de Profilers que já usei, o do VS é okay, meu favorito? nProfiler, como a ferramenta é cerca de R$ 3000, vou demorar um pouco pra adquirir e apresentar a diferença em um post futuro.

Voltando ao assunto…

Coloquei o breakpoint em 5 pontos:

  • Na primeira linha do main.
  • Antes de carregar os mods pra memória.
  • Antes de carregar as alterações do jogo base.
  • Carragando as mudanças do Mod.
  • Última linha.
Bom perceberam? quando clica nos eventos na parte superior dá pra identificar a quantidade de GC, Memória e CPU está sendo consumido, as setas amarelas são execuções de limpeza do GC 😉

essa é uma forma, fácil de saber qual dos itens você quer começar a otimização, quer uma mais fácil ainda sem breakpoints?

Alt+F2, selecione CPU Usage e rode o projeto.

só faltou o VS programar pra você agora né?

Vamos nos focar nesse caso aqui

faz sentido demorar eim..

Várias iterações que podem ser simplificadas.

Dados coletados, bora pro código

Vamos dar um tapa nessa parte, e mensurar os ganhos.

O Commit da alteração e o resultado:

de certa forma melhorou, reduzimos um pouco o GC, aumentou o tempo para processamento mas já esperávamos, já que não processamos mais paralelamente as alterações, Estamos dependendo do IO, dá pra fazer mais nada… será?

O Inimigo agora é outro:

Mais uma iteração no commit mudanças:

  • Mudei para acessar o dictionary diretamente e atualizar o objeto
  • Remoção do concurrentBag.
  • Alteração de classes para structs de ModListChanges e DetailChanges

Gen 0:499
Gen 1:103
Gen 3:10

estamos no caminho certo, começando respeitar o GC, executamos mesmo processo e em alguns segundos mais rápido, está bom, mas vamos buscar a excelência, ou até onde minha capacidade der, mas vocês podem me mandar msg pra melhorarmos ainda mais 🙂

Notas:

  • Percebeu como eu ignorei o fato que estou usando 3 objetos estáticos em uma classe nada haver “Helpers”?
    pois é, o foco está em resolver o problema depois pensamos em uma forma elegante de organizar o código.
  • Tenho quase certeza que estava usando o updateFactory e addFactory errado naquele dictionary :x, nem vou olhar porque deu vergonha
  • paramos de atualizar um objeto em memória sem necessidade no DetailChanges.
  • Simplifiquei a forma que era gerado o Hash.

Vou encerrar mais esse post, acredito que temos grandes lições aprendidas.
E lembrem-se para tomar uma ação precisamos testar, mensurar, mudar e repetir.

Próximo post.. quando eu tiver tempo :p