Lei de Amdahl: TypeScript 7 é 10x mais rápido. Sua CI continua lenta
🇬🇧 Read it in English
A CI da sua aplicação vai ficando mais lenta com o tempo. Um pouco a cada mês, até o dia em que ela deixa de ser um incômodo e vira um impedimento.
Qual é o seu primeiro passo?
Vi isso acontecer semana passada.
A CI de um serviço Node + TypeScript levava 15 minutos. Testes de unidade com coverage, uns 1.200 testes.
E o raciocínio que aparece na hora é sempre o mesmo: se está lento, tem algo errado, deve dar pra otimizar. Atualiza o TypeScript pra versão em Go, que faz typecheck muito mais rápido. Troca o eslint e o prettier pelo ox, que são muito mais rápidos.
Nada disso é falso. O TypeScript em Go é absurdamente mais rápido. O oxlint é mais rápido que o eslint.
E mesmo assim não ia consertar nada.
O TypeScript 7 é tudo isso mesmo
O tsc era um compilador de TypeScript escrito em TypeScript, rodando em Node. A 7 é uma porta fiel dele para Go, em binário nativo, com type-checking paralelo em 4 threads.
Os números da Microsoft não são modestos: VS Code caiu de 125,7s pra 10,6s, Sentry de 139,8s pra 15,7s. No editor, abrir um arquivo com erros saiu de ~17,5s pra menos de 1,3s.
O JavaScript emitido é o mesmo, o sistema de tipos é o mesmo. Isso é uma história de compile-time, ponto final.
Um detalhe pra quem for migrar: a 7.0 não tem API programática, ela só chega na 7.1. Qualquer ferramenta que embute o compilador - typescript-eslint, Volar, e por tabela Vue, Svelte, Astro, Angular - continua no TypeScript 6.
A faca japonesa e o feijão
A lei de Amdahl explica por que trocar o compilador não ia mudar nada:
p é a fração do trabalho que você melhorou, s é o quanto melhorou. A parte que dói é o limite: se s tender ao infinito, o speedup máximo do sistema inteiro vira 1 / (1 - p).
Ou seja: o pedaço que você não otimizou é o seu teto. E ele não se move.
Pensa no almoço de domingo. Você leva 40 minutos. Compra uma faca japonesa cara que corta cebola 10x mais rápido, e a propaganda não mentiu. Só que cortar cebola levava 2 minutos, e agora leva 12 segundos.
O almoço continua levando 38 minutos e pouco.
Porque o que demora é o feijão na panela. E feijão não é problema de corte, é problema de espera. Faca nenhuma resolve espera.
Fizeram a atualização
Subiram tudo junto: eslint 8.57 -> oxlint 1.76, prettier 3.0 -> oxfmt 0.61, TypeScript 5.9 -> TypeScript 7.
| passo | antes | depois |
|---|---|---|
lint |
5,56s | 3,73s |
format:check |
6,51s | 3,42s |
typecheck |
4,46s | 4,04s |
typecheck dos testes |
4,50s | 3,93s |
| total | 21,03s | 15,12s |
As engines novas entregaram os 6x a 10x prometidos. O tsc 7 compila o projeto inteiro em 1,22s. O que come a diferença é o overhead fixo de cada invocação: subir o processo e carregar a ferramenta custa mais que o trabalho em si.
Resultado líquido: 5,91 segundos. De 900.
E olha que esses quatro passos somados eram 21s numa pipeline de 900s, ou seja p = 2,3%. Se as ferramentas fossem infinitamente rápidas, custo zero:
No cenário impossível, a CI cairia de 15min00 pra 14min39. Esse era o teto, e ele estava disponível antes de qualquer linha de código, com uma divisão de trinta segundos.
Isso não faz do benchmark da Microsoft mentira. O Slack cortou type-checking na CI de 7,5 minutos pra 1,25 minuto, só que lá é um passo dedicado de tsc, onde o compilador é 100% do trabalho. Mesmo compilador, mesmo ganho de engine, resultado oposto: a diferença não está na ferramenta, está no denominador.
Medir custa cinco minutos
O GitHub Actions já mostra a duração de cada step na UI, de graça. Abra a última run e leia antes de qualquer teoria. Depois divida o tempo do passo pelo tempo total: aquilo é o máximo que você pode ganhar atacando ele. Se der 2%, você acabou de economizar uma semana.
Se precisar cavar mais fundo, as ferramentas já existem e são subutilizadas de um jeito criminoso:
jest --verbosete dá a duração por arquivo. Quase sempre 3 arquivos respondem por metade do tempo.jest --detectOpenHandlesacha promise que não resolve e timer que não limpa, que deixam o runner sentado esperando o event loop drenar depois que os testes já acabaram. Existe faz anos.
O Knuth escreveu isso em 1974, na mesma página do Structured Programming with go to Statements de onde saiu o “premature optimization is the root of all evil” que todo mundo cita pela metade:
“É frequentemente um erro fazer julgamentos a priori sobre quais partes de um programa são realmente críticas, já que a experiência universal dos programadores que usam ferramentas de medição é que os palpites intuitivos deles falham.”
O problema é o pulo
Nada disso quer dizer “não atualize”. Atualize - o TypeScript 7 é um feito de engenharia impressionante e eu vou migrar tudo que der. Aqueles 6 segundos não mudam a CI, mas mudam o loop do agente, que roda typecheck e lint dezenas de vezes por hora. É ganho real, só que em outro número.
O que está errado é outra coisa: ir de “está lento” direto pra “troca por algo mais rápido”, sem a divisão de trinta segundos no meio. Velocidade de ferramenta não é velocidade de sistema.
E trocar ferramenta é confortável porque parece produtivo: tem PR, tem changelog, tem benchmark bonito pra mostrar. Medir primeiro parece burocracia.
Mas medir é o que separa engenharia de torcida.
Por hoje é só.
- Announcing TypeScript 7.0 - o anúncio oficial, com os benchmarks e a lista completa de breaking changes.
- TypeScript 7 Went Native: What Actually Changes and What Doesn’t - boa leitura sobre o que a migração não muda.
- Systems Performance, do Brendan Gregg. Se você só for ler um capítulo, leia o 2.