Perfilamento de código JavaScript: encontrando gargalos
\n\nQuando seu projeto Node.js começa a ficar lento, o primeiro pensamento é "preciso reescrever tudo em Rust". Mas não se apresse! Na maioria das vezes, o problema está em algumas linhas de código que funcionam de forma ineficiente. Neste artigo, vamos explorar como encontrar esses "gargalos" usando ferramentas simples de perfilamento. Você aprenderá como medir o desempenho, construir um flamegraph e entender para onde vai a CPU.
\n\nPor que perfilar o código?
\n\nImagine que seu servidor processa 100 requisições por segundo, mas precisa processar 500. Você pode otimizar tudo, mas isso levaria semanas. Ou você pode encontrar uma única função que consome 80% do tempo e corrigi-la em uma hora. O perfilamento é exatamente o que mostra onde o tempo está sendo gasto.
\n\nVamos considerar os principais cenários em que o perfilamento é necessário:
\n- \n
- Alta latência nas respostas da API \n
- Vazamentos de memória (memory leaks) \n
- Alta carga na CPU sem motivo aparente \n
- Processamento lento de grandes conjuntos de dados \n
Ferramenta 1: Profiler integrado do Node.js (--prof)
\n\nA maneira mais simples é executar o Node.js com a flag --prof. Ele coleta estatísticas de execução e as salva em um arquivo isolate-*.log.
Vamos criar um script de teste que contém um gargalo:
\n\n// slow.js\nfunction heavyComputation() {\n let sum = 0;\n for (let i = 0; i < 1e7; i++) {\n sum += Math.sqrt(i) * Math.sin(i);\n }\n return sum;\n}\n\nfunction fastFunction() {\n return 42;\n}\n\nconsole.time('total');\nheavyComputation();\nfastFunction();\nconsole.timeEnd('total');\n\n\nVamos iniciar o perfilamento:
\nnode --prof slow.js\n\nApós a execução, um arquivo isolate-0x...-v8.log será gerado. Para lê-lo, use o utilitário node --prof-process:
node --prof-process isolate-0x...-v8.log > profile.txt\n\nAbra o profile.txt e veja a tabela com as porcentagens de tempo:
[Summary]:\n ticks total nonlib name\n 785 78.5% 85.3% JavaScript\n 215 21.5% 14.7% C++\n ...\n[Bottom up (heavy) profile]:\n ticks parent name\n 500 50.0% LazyCompile: *heavyComputation slow.js:2:17\n 100 10.0% Builtin: MathSqrt\n 50 5.0% Builtin: MathSin\n\n\nVemos que 50% do tempo é gasto na função heavyComputation. Agora sabemos o que otimizar primeiro.
Ferramenta 2: Chrome DevTools (inspetor)
\n\nPara uma análise mais visual, vamos conectar o Chrome DevTools. Execute o Node.js com a flag --inspect:
node --inspect slow.js\n\nAbra o Chrome e vá para chrome://inspect. Clique em "Open dedicated DevTools for Node". Aba "Profiler" → "Start" → execute o script → "Stop".
Você verá um flamegraph — um gráfico onde a largura do bloco mostra o tempo de execução. Quanto mais largo o bloco, mais "quente" é a função. Nosso heavyComputation será um enorme retângulo.
Dica: Use o filtro "Focus selected" na função para ver suas chamadas internas. Por exemplo, você notará que Math.sqrt é chamado 10 milhões de vezes — talvez valha a pena pré-calcular as raízes.
Ferramenta 3: perf_hooks — medições precisas no código
\n\nÀs vezes, é necessário medir um trecho específico do código sem executar ferramentas externas. Para isso, use o módulo integrado perf_hooks.
Exemplo de medição do tempo de execução de uma função:
\n\nconst { performance } = require('perf_hooks');\n\nfunction processData(items) {\n let result = [];\n for (let item of items) {\n // Simulação de processamento complexo\n for (let j = 0; j < 1000; j++) {\n result.push(item * j);\n }\n