Escolha da linguagem
O que muda de verdade entre Node, Python, Go, Java e C# em modelo de execução, ecossistema e operação.
A pergunta errada e a pergunta certa
"Qual é a linguagem mais rápida?" é a pergunta mais comum e a menos útil. Num backend típico, um pedido passa a maior parte do tempo esperando: o banco responder, o cache responder, outro serviço responder. A linguagem decide o que acontece nos poucos milissegundos entre essas esperas.
As perguntas úteis são outras:
- Como a linguagem executa o código e quanto tempo leva para iniciar?
- Como ela atende muitos pedidos ao mesmo tempo?
- Quanta memória ela ocupa, e como se comporta dentro de um container?
- Quais bibliotecas existem para o problema, e quem na equipe sabe usá-las?
Este tópico compara as cinco linguagens usadas nos exemplos da trilha por esses critérios.
Como cada uma executa o código
| Linguagem | Execução | Consequência |
|---|---|---|
| Node (JavaScript, TypeScript) | Interpretada e compilada durante a execução (JIT) pelo V8 | Partida rápida; o TypeScript é convertido em JavaScript antes |
| Python | Interpretada pelo CPython | Partida rápida, execução de código Python puro mais lenta |
| Go | Compilada antes, para um binário nativo | Partida muito rápida, um único arquivo para implantar |
| Java | Compilada para bytecode e depois otimizada pela JVM durante a execução (JIT) | Partida mais lenta, desempenho que melhora depois de minutos de uso |
| C# | Compilada para bytecode e otimizada pelo .NET durante a execução (JIT), com opção de compilação nativa | Partida intermediária, desempenho alto depois do aquecimento |
A compilação durante a execução tem um efeito pouco lembrado: o código fica mais rápido depois de rodar por um tempo. Medimos isso com um servidor Java (Jetty) limitado a uma CPU: 2.500 pedidos por segundo nos primeiros 15 segundos, 5.500 nos 15 seguintes e 10.000 depois de 30 segundos. Um serviço que acabou de iniciar, numa escala automática, atende os primeiros pedidos com desempenho bem menor que o de regime.
Como cada uma atende muitos pedidos
Este é o ponto em que as linguagens mais diferem, e onde moram as armadilhas.
| Linguagem | Modelo | Armadilha principal |
|---|---|---|
| Node | Um laço de eventos numa única thread; entrada e saída não bloqueiam | Um cálculo pesado trava todos os pedidos ao mesmo tempo |
| Python | Uma trava global (GIL) deixa só uma thread executar código Python por vez; asyncio para entrada e saída | Código síncrono dentro de uma rota assíncrona trava o laço; paralelismo de CPU exige vários processos |
| Go | Goroutines leves, distribuídas pelo runtime entre as CPUs | Goroutines que nunca terminam vazam memória em silêncio |
| Java | Threads do sistema; desde o Java 21, threads virtuais leves | Bibliotecas antigas que bloqueiam threads de plataforma |
| C# | async/await sobre um pool de threads | Chamar .Result ou .Wait() em código assíncrono esgota o pool |
O padrão comum: as cinco atendem bem milhares de conexões esperando entrada e saída, cada uma do seu jeito. Os problemas aparecem quando o código bloqueia onde o modelo espera que ele não bloqueie, ou quando há trabalho pesado de CPU. O tópico de concorrência aprofunda os modelos.
Memória e partida, medidas
Construímos o mesmo servidor mínimo (uma rota que devolve um JSON pequeno) em cada linguagem, como imagem de container, e medimos com cada container limitado a uma CPU e 512 MB de memória:
| Imagem | Partida até o primeiro pedido | Memória parada | Memória sob carga | |
|---|---|---|---|---|
Node 24 (node:http) | 235 MB | 0,34 s | 18 MB | 23 MB |
| Python 3.13 (FastAPI e Uvicorn) | 212 MB | 0,70 s | 44 MB | 48 MB |
Go (net/http) | 14 MB | 0,18 s | 6 MB | 15 MB |
| Java 21 (Jetty 12) | 461 MB | 1,44 s | 73 MB | 106 MB |
| C# .NET 9 (ASP.NET) | 331 MB | 0,41 s | 17 MB | 27 MB |
Esses números importam de verdade em três situações: serverless e escala a partir de zero, em que a partida é paga pelo usuário que espera; muitos serviços pequenos, em que a memória de cada um se soma; e escala automática rápida, em que imagem e partida definem quanto tempo uma cópia nova leva para ajudar.
E a velocidade?
No mesmo teste, medimos quantos pedidos por segundo cada servidor atendeu, com 50 conexões simultâneas:
| Pedidos por segundo | Latência p99 | |
|---|---|---|
| Go, ajustado para uma CPU | ~55.000 | 2 ms |
| Node | ~32.000 | 3 ms |
| C# | ~31.000 | 54 ms |
| Java com Jetty, depois de 30 s de aquecimento | ~10.000 | 77 ms |
| Python com FastAPI, um processo | ~3.200 | 27 ms |
O que o teste ensinou sobre medir
As primeiras rodadas deram números errados, e cada erro é uma lição sobre como as linguagens se comportam em containers:
| Primeiro resultado | Causa | Depois de corrigir |
|---|---|---|
| Go: 7.000 pedidos por segundo, p99 de 84 ms | O Go via as 8 CPUs da máquina, e não o limite de 1 CPU do container; o sistema operacional freava o processo | Com GOMAXPROCS=1: 55.000 pedidos por segundo, p99 de 2 ms |
| Java: 1.200 pedidos por segundo, latência fixa de 41 ms | O servidor HTTP embutido no JDK, com uma interação conhecida entre o algoritmo de Nagle e as confirmações atrasadas do TCP | Trocado por um servidor de produção (Jetty) |
| Java com Jetty: 3.400 pedidos por segundo | Só 5 segundos de aquecimento do JIT | 10.000 depois de 30 segundos |
A primeira linha é a mais importante para quem usa Go em produção. O Go 1.25 passou a considerar o limite de CPU do container, mas com um mínimo de duas threads e só quando o go.mod declara a versão 1.25 ou mais nova. Com o go.mod na versão 1.24, ele continuou vendo as 8 CPUs da máquina. O nível Implementação mostra como cada runtime enxerga os limites do container.
Por que isso importa quando a IA escreve o código
Um assistente escreve código correto nas cinco linguagens, mas raramente leva em conta o modelo de execução de cada uma:
- Um cálculo pesado ou uma leitura síncrona de arquivo dentro de uma rota Node trava o servidor inteiro.
- Uma biblioteca síncrona chamada dentro de uma rota
asyncdo Python trava o laço de eventos. - Um
.Resultem C# ou umsynchronizedlongo em Java prende as threads sob carga. - A configuração de memória e CPU do container é ignorada, e o processo morre por falta de memória ou é freado.
- A escolha da linguagem é justificada por um benchmark de "olá mundo".
O próximo nível compara as linguagens pelos critérios que costumam decidir a escolha.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Escolher a linguagem em cinco minutos
Explique em no máximo cinco minutos de leitura o que realmente muda entre Node, Python, Go, Java e C# para construir backends, para alguém que conhece só uma delas. Cubra só o essencial: como cada uma executa o código, como atende muitos pedidos ao mesmo tempo, quanto ocupa de memória e por que a velocidade raramente é o que decide. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
Modelos de execução e concorrência
Quero entender como Node, Python, Go, Java e C# executam código e atendem pedidos simultâneos. Aja como um mentor de backend. Pergunte primeiro quais dessas linguagens eu já uso. Para cada linguagem, uma por vez, explique: 1. Como o código é executado: interpretado, compilado antes ou compilado durante a execução. 2. O modelo de concorrência: laço de eventos, GIL e asyncio, goroutines, threads e threads virtuais, async e await. 3. O que acontece quando uma rota faz um cálculo pesado de CPU. 4. O que acontece quando uma rota chama uma função que bloqueia. Depois de cada linguagem, faça uma pergunta de cenário e espere a minha resposta. No final, monte comigo uma tabela comparando as cinco.
Por que a linguagem raramente é o gargalo
Quero entender por que a escolha da linguagem raramente define o desempenho de um backend. Explique, com números aproximados e deixando claro que são ordens de grandeza: 1. Quanto tempo uma rota típica gasta em consultas ao banco, chamadas a outros serviços e no próprio código. 2. O que um benchmark de "olá mundo" mede e o que ele não mede. 3. Em que tipos de sistema a linguagem passa a fazer diferença: processamento de CPU, latência muito baixa, custo de memória com muitos serviços, serverless. 4. Como a compilação durante a execução (JIT) afeta os primeiros minutos de um serviço. Depois me apresente quatro sistemas e pergunte se a linguagem seria decisiva para o desempenho de cada um. Corrija as minhas respostas.
Runtimes dentro de containers
Quero entender como cada runtime se comporta dentro de um container com limites de CPU e memória. Explique, para Node, Python, Go, Java e C#: 1. Se o runtime percebe o limite de CPU do container, e o que acontece quando ele acha que tem mais CPUs do que tem. 2. Se o runtime percebe o limite de memória, e como define o tamanho do heap ou o momento de coletar lixo. 3. Quais configurações ajustam esse comportamento. Depois, me ajude a rodar um pequeno programa em cada linguagem que mostra o que o runtime enxerga, num container com --cpus=1 e --memory=512m, e explique os resultados.