explorando a bolha tech: Bare-Metal
Esse é o primeiro de prováveis muitos outros posts, onde eu vou explorar a bolha de tecnologia e tentar entender como os dev de cada área escolhem suas tecnologias e descobrir porque certas tecnologias são mais populares que outras.
Atenção!
-
Eu não sou nenhum grande profissional com décadas de experiência, tudo citado nesse post é apenas minha opinião feita com base nas minhas pesquisas pessoais, sugestões, correções e críticas são bem vindas.
-
apesar do termo bare-metal, o post trata exclusivamente de sistemas microcontrolados.
A Bolha Bare-Metal:
Antes de tudo, embarcados é uma área enorme, então vamos definir desenvolvimento embarcado bare-metal como qualquer coisa desde um Attiny e que não seja capaz de rodar um OS (exceto microkernels).
dito isso, vamos tentar entender as necessidades da área passando por tópicos como memória, performance e portabilidade e assim tentar entender porque lang como Rust e Zig tem mais atenção nesse nicho que Go ou D e porque langs como Java e python são usadas em ambientes tão limitados?
gerenciamento de Memória:
não é surpresa pra ninguém que memória nesses ambientes é limitada, então só se deve usar langs que fazem pouco uso da heap e evitar tudo que usa GC certo? na verdade não é tão simples, claro, menos gasto de memória é sempre bom, e hardwares simples como attiny não tem opção, mas hardwares mais parrudos como os da familia ESP 32 e outros MCU médios como os STM32Fxxx poderiam facilmente suportar um GC, na verdade ESP32 e STM32 são famílias populares para makers que usam Python, Lua e até JS, mas então, se ter GC não é problema para makers, porque profissionalmente é?
Porque o problema não é o GC, nunca foi, o problema é ele ser obrigatório, veja linguagens populares como C++ e Rust possuem meios de imitar GC-like, usando ARC, eles tem gerenciamento dinâmico de memória, só não é obrigatório.
Ok e porque ser obrigatório é um problema? existem vários motivos, mas o principal deles é:
non-deterministic behavior:
em ambiente bare-metal profissional existe uma necessidade chamada “real-time”, que significa que tarefas tem um tempo específico para poder executar, coisa como GC podem adicionar tempo indesejado a essa tarefa, visto que não tem como controlar exatamente o tempo de execução, porque durante esse tempo, são chamadas funções que você como programador não tem controle e afeta a performance da tarefa, um exemplo
imagine um carro autônomo a 250Km/h , esse carro verifica o sensor de presença a cada 50ms, imagina que no meio da verificação o GC ativou, parou a verificação, e gastou 100 ms, o sensor volta e detecta algo e ativa o freio, mas por conta dessas 100ms a mais não dá tempo de parar o carro e alguém é atropelado.
(Esse exemplo é obviamente exagerado, uma tarefa dessas teria altíssima prioridade, talvez até crítica)
fragmentação de memória:
alguns controladores não tem MMU outros tem muita pouca memória, alocar memória e desalocar pode gerar problemas como fragmentação, o que não é problema pra langs como Rust, Zig, C++ porque nesses casos é só evitar a heap, é aí que langs como Go e Java perdem popularidade, mesmo sendo langs de sistemas, elas podem alocar na heap sem você saber
(elas ainda são opções boas nos MCUs que não tem esse problema, mas esse topico causa um efeito na popularidade geral)
hidden allocations:
langs que usam GC normalmente usam desse mecanismo para esconder coisas do Dev, regras de inicialização, alocações automáticas, Exception (depende da lang), etc…, isso cai nos mesmo 2 problemas citados acima, falta de controle temporal e fragmentação
(hidden allocations na stack também são um problema, e langs como C++ e Rust também sofrem com isso, apenas não foi citado porque o tópico é sobre heap).
OK, agora nós sabemos que apesar da memória limitada, muitos MCUs não tem problema lidando com GC, e que o principal problema nesses MCUs são as questões temporais associadas ao tempo de execução.
Performance:
esse é um tópico interessante, e extremamente complexo de se chegar a uma conclusão, mesmo que cada lang tenha um overhead na performance baseado na quantidade de features, boa parte da performance vem da qualidade compilador, afinal é ele que vai gerar as instruções, mas uns processadores de MCUs são super simples e tem poucas instruções,e com opções limitadas de compiladores e outros são o completo oposto; fica difícil analisar performance de forma justa, claro C++ vai ser mais MUITO mais rápido que micropython, mas agr comparar C++, Rust, C, Zig e outras langs compiladas, é muito complexo.
mas de qualquer forma, se engana quem acha que todo projeto em bare-metal é focado 100% em performance, na verdade, devido a o real-time, se a lang que você usar comprir as necessidades temporais do projeto, tanto faz o resto.(apenas modo de dizer, performace é importe)
Portabilidade:
é aqui que a maioria das langs morrem, até o momento eu falei como se todas as langs citadas tem portabilidade para tudo, mas a verdade tá longe disso, temos várias arquiteturas AVR, PIC, XTENSA, ARM-CORTEX, Risc-V…etc, e cada MCU pode ter um set de features completamente diferentes entre si, é a portabilidade que vai definir o grau de aceitação de uma lang
linguagens como Go, Java, Python, JS, Lua etc… rodam num número limitado de hardwares, diria que Go seja capaz de rodar em umas 100 placas (TinyGo marca 85, mas tem uns quebrados a mais) por aí, mas só a família STM32 tem mais de 1300 por exemplo.
uma das principais características para uma boa portabilidade no bare-metal é o freestanding, o quão independente uma linguagem é de um sistema/runtime, ele tem q ter:
- meios de acessar diretamente o hardware (ponteiro)
- o mínimo possível hidden allocations e hidden control flow
- suporte para no standard library
- suporte para várias archs e targets dessas archs (aqui ta incluso os meios de representar features do hardware, linkerscripts e etc...)
Todos os langs com GC caem aqui, como já falamos elas tem hidden allocations, e tem targets que não suportariam alocações, e obviamente, depende de uma biblioteca padrão para poder gerenciar a memória.
(ISSO É SOBRE POPULARIDADE, você pode usar o seu Go ou o seu Java embarcado, novamente, só estou falando da aceitação profissional)
C++ também sofre bastante, apesar de não ter nada de muito especial, Exceptions, RTTI, herança,** virtual** até a keyword new ( em AVR)
(curiosidade: C++ na verdade é uma linguagem bastante underrated em bare-metal, sua maior força vem com linux embarcado)
,
Um dos principais fatores da popularidade de Rust em sistemas, se dá porque a lang diretamente corrige esses problemas de C++, ou você achava que Result<Ok, Err> foi só questão de gosto?
mas Rust também não escapa, ele corrige os problema de C++, mas tem seus próprios, isso pode me fazer ser apedrejado mas… Rust é meio bloated.
e por fim tem C, a linguagem é estupidamente simples, tem compila até pra OVNI marciano, é não depende de quase nada, só no Brasil, 70% dos projetos embarcados é C
(https://embarcados.com.br/relatorio-da-pesquisa-sobre-o-mercado-brasileiro-de-sistemas-embarcados-e-iot-2023/)
C é de longe a campeã do bare-metal, sem nem competição….por enquanto, Zig 0.12 ta vindo com força, todas as vantagens do C, unido com as vantagens de uma lang moderna, se vc gosta de bare-metal, experimente Zig, vc não vai se arrepender.
No geral é isso, obrigado por ler até aqui, embarcados é uma área enorme e eu só conseguir citar tópicos superficiais, quem sabe eu faça uma parte 2 com topicos mais profundos, qualquer sugestão, crítica ou correção, por favor deixe nos comentários, eu vou adorar ler, bom… é isso tchau.