2024-10-05 – Sistema de Gestão de Threads finalizado no meu Game Engine em C++…
Já finalizei o sistema de gestão automático de threads no meu Game Engine.
E porquê usar threads em primeiro lugar? Simples. Se temos um computador com por exemplo uns 8 cores e 8 threads, de que nos serve correr a aplicação em apenas uma thread, onde teríamos no máximo 12,5% (16,6% se usasse 6 threads das 8) de potencial uso do CPU, o que significa que nunca usaríamos os 100% que o CPU nos proporciona, e ficaríamos com tudo mais lento.
Para isso, temos obviamente de usar Multi-Threading, dividir a carga dos nossos programas em threads que correm em threads/cores distintos para usarmos não 12,5% do CPU mas sim uns 30%, 50%, 70%, e com isso ter muito mais FPS e um jogo muito mais rápido.
No começo eu usava Multi-Threading ocasionalmente porque estive mais preocupado com o que ia fazendo no Game Engine, como sistemas de água, chuva, neve, balística, armas, etc.
Mas chegamos a uma altura em que temos de começar a pensar num sistema de gestão dinâmica de threads, para onde possamos enviar tarefas para que o tal sistema decida que threads usar, divide o trabalho entre elas, entre outras coisas, para que tudo funcione na perfeição.
Abaixo podemos ver as threads obreiras, às quais chamei de “TaskRunner”, porque correm as tarefas que para elas envio:
No exemplo acima podem ver à direita, as várias threads de nome “TaskRunner” que tenho activas, a aceitar dividir entre elas carga, mas neste caso usei-as apenas para as gotas de chuva, para que desse uma percentagem de carga relativamente igual entre elas, e coloquei um número em cada uma delas para ficar com um aspecto mais giro, como “TaskRunner[01]”, “TaskRunner[02]”, etc.
Acima uso o comando top no Linux para visualizar as mesmas, no meio à direita, e o htop acima à direita, mas o top neste caso é melhor para o efeito.
No vídeo anterior, eu tinha de propósito criado as threads em tempo real em cada loop do Game Engine, ou seja, em casa FPS (Frame Por Segundo), e por isso podiam ver que o número das threads mudava o tempo todo, o que era mau, mas que fiz apenas para motivos didácticos, como exemplo:
Claro que isto é errado, pois imaginem que eu tenho uns 300 FPS no jogo, e que em cada um desses 300 FPS (300 loops), eu abriria umas 20 threads para dividir carga, isto significaria que eu estaria a sobrecarregar o Kernel umas 300*20=6000 vezes por segundo! Isto é errado, tornaria tudo mais lento, e sobrecarregaria o sistema.
Obviamente que no exemplo anterior apenas o fiz para poderem ver uma thread a ser gerada dinamicamente sempre que activava o sistema de chuva, e podem ver que o PID dessa única thread que tratava da chuva estaria sempre a mudar, e mudaria umas 300 vezes por segundo se fossem 300 FPS, só a vêem mudar pouco devido ao tempo de actualização do htop no exemplo.
Hoje é diferente, como mostrei acima, vocês já vêem que o PID sempre igual nessas threads, e as mesmas já identificadas para fácil consulta e debugging:
Assim, não estamos a criar centenas ou milhares de threads em cada segundo, o que seria simplesmente parvo.
Este é o exemplo anterior, feito mal de propósito, onde era invocada só uma thread para tomar conta de tudo, criada em tempo real em cada loop (daí o seu PID mudar sempre), ao invés de termos várias a trabalhar num bocadinho da tarefa para tornar tudo mais rápido (terminam mais rápido do que se fosse só uma), e ao mesmo tempo só são criadas no arranque do Game Engine e não em cada loop:
Nas linguagens mais fáceis como Java e C# e outras, ninguém tem de se preocupar com este tipo de coisas.
Mas em C ou C++ isto tem de ser pensado ao pormenor. Podemos ter poder e performance, mas temos sempre um preço a pagar, com a complexidade da coisa.
Podem ver também que os milhares de gotas de chuva, ocupam uns até 60% de uma thread, e no meu caso, como uso computadores fracos (portáteis de uns 500 a 700 euros) mesmo para me forçar a optimizar código, em que só tenho 8 threads disponíveis, só criei 6 threads para estes trabalhos (calculo as mesmas no arranque consoante o potencial de cada hardware da máquina que executa o meu Game Engine), e decidi dividir a carga sempre entre todas.
Assim, eu poderia ter só uma a trabalhar a 60% da sua capacidade (que numa máquina com 8 cores seria 60% de 12.5% (pois 100%/8 threads=12.5), ou seja 7,5%.
Mas o meu sistema decide em tempo real dividir neste caso a carga pelas 6 threads dado que estão disponíveis e só estou a usar as mesmas para o sistema de chuva, e daí podem ver que cada uma se esforça menos, cada uma indo apenas a 12% do seu potencial, o que é muito bom.
Mais tarde incluirei aqui muitas outras tarefas, como talvez certas explosões ou animações mais complexas, cálculos de balística, desenho de níveis, etc, e aí o meu sistema irá dividir a carga em tempo real consoante o que ele vê que essas tarefas requerem do processador.
No começo são estudadas, e depois tudo funciona na perfeição.
Este sistema tem uma vantagem, é que nunca mais me preocupo com threads, sempre que quero abrir algo numa nova thread, simplesmente mando a tarefa para o meu gestor, e ele trata de tudo sozinho.
De qualquer das formas este post é não só parte do meu diário de desenvolvimento, como também algo que pode ser usado para fins académicos.
E claro, aproveitei para mostrar a beleza dos paineis publicitários que criei no meu dia de anos:
E que se baseiam no jogo Tricky Quiky dos anos 90:
Bem, agora como tenho tido pouco tempo farei pausa de alguns dias de novo, e mais tarde melhorarei o nível de testes de chuva para que funcione com este novo sistema, antes de saltar para novas aventuras no desenvolvimento do meu Game Engine.
Até à próxima, hasta!
2024-10-05/06.
Publicado no mesmo dia no meu LinkedIn em 2024-10-09, com o texto:
«O que mostrei da outra vez foi só para fins didádicos, com threads geradas em real-time às centenas por segundo (nada de prático).
Aqui já têm as minhas threads que correm tasks bem definidas a fazer o seu trabalho.»
Em:
Próximo post associado ao meu Game Engine (também associadl a Multi-Threading, Chuva e Neve, e o nível de testes):
Post anterior associado ao meu Game Engine (e também ao sistema de Multi-Threading):
O Post associado aos placards publicitários que mencionei aqui: