Introdução: por que o perfilamento é o primeiro passo para a otimização
\nOtimizar código sem perfilamento prévio é como atirar no escuro. Frequentemente, desenvolvedores gastam horas "melhorando" trechos de código que na verdade não são o gargalo. O perfilamento de código JavaScript permite identificar exatamente quais funções consomem mais tempo de CPU, onde ocorre vazamento de memória e quais operações bloqueiam o Event Loop.
\nNeste artigo, vamos explorar métodos práticos de perfilamento no ambiente Node.js, usando tanto ferramentas integradas quanto recursos do navegador. Você aprenderá a encontrar problemas reais de desempenho, sem precisar adivinhar.
\n\n1. Ferramentas para perfilamento Node.js
\nO Node.js oferece várias ferramentas poderosas, mas muitas vezes subestimadas. Vamos considerar três abordagens principais.
\n\n1.1. Profiler V8 integrado
\nA maneira mais simples de obter um perfil é executar o script com a flag --prof:
node --prof app.js\nApós a execução do script, um arquivo isolate-0x...-v8.log é criado. Para convertê-lo em texto legível, use:
node --prof-process isolate-0x...-v8.log > profile.txt\nExemplo de saída (simplificada):
\nStatistical profiling result from isolate-0x3a00000-v8.log, (1002 ticks, 1 unaccounted, 0 excluded).\n\n [Shared libraries]:\n ticks total nonlib name\n 10 1.0% /lib/x86_64-linux-gnu/libc-2.31.so\n\n [JavaScript]:\n ticks total nonlib name\n 450 44.9% 45.5% LazyCompile: *processData /home/user/app.js:12:17\n 200 20.0% 20.2% LazyCompile: *fetchFromDB /home/user/app.js:34:23\n 100 10.0% 10.1% Builtin: CallFunctionReceiver\n\nAqui, vemos que a função processData consome quase metade do tempo — um alvo claro para otimização.
1.2. Chrome DevTools para Node.js
\nPara uma análise mais visual, use o Chrome DevTools. Execute o aplicativo com a flag --inspect:
node --inspect app.js\nAbra chrome://inspect no Chrome e clique em "Open dedicated DevTools for Node". Você verá as abas Profiler (ou Performance nas versões mais recentes). Grave um perfil executando a ação desejada no aplicativo e analise o Flame Graph (gráfico de chamas).
O Flame Graph mostra quais funções chamam outras e quanto tempo consomem. "Barras" largas indicam as funções mais custosas.
\n\n1.3. console.time e console.timeEnd
\nPara medições rápidas e pontuais, use o par console.time / console.timeEnd:
console.time('parseJSON');\nconst data = JSON.parse(largeString);\nconsole.timeEnd('parseJSON'); // parseJSON: 12.345 ms\nEste método não requer a execução de um profiler separado e é ideal para depuração local. No entanto, lembre-se: os resultados são influenciados pela carga atual da CPU, portanto, faça várias medições.
\n\n2. Exemplo prático: encontrando um gargalo em um servidor web
\nImagine que temos um servidor HTTP simples em Express que processa requisições, carrega dados de um arquivo e realiza cálculos complexos. Nossa tarefa é descobrir por que as respostas ficam lentas sob carga.
\n\n2.1. Código original (problemático)
\nconst express = require('express');\nconst fs = require('fs');\nconst app = express();\n\napp.get('/data', (req, res) => {\n console.time('total');\n \n // Leitura de arquivo síncrona (operação bloqueante)\n const fileData = fs.readFileSync('./large-file.json', 'utf8');\n \n // Parse do JSON\n const json = JSON.parse(fileData);\n \n // Transformação complexa\n const result = json.map(item => {\n let sum = 0;\n for (let i = 0; i < 10000; i++) {\n sum += Math.sqrt(item.value * i);\n }\n return { id: item.id, sum };\n })