Perfilado de código JavaScript: encontrando cuellos de botella
\n\nCuando tu proyecto Node.js empieza a ir lento, el primer pensamiento es «tengo que reescribir todo en Rust». ¡Pero no te apresures! La mayoría de las veces el problema está en unas pocas líneas de código que funcionan de manera ineficiente. En este artículo, veremos cómo encontrar esos «cuellos de botella» usando herramientas de perfilado simples. Aprenderás a medir el rendimiento, construir un flamegraph y entender dónde se consume la CPU.
\n\n¿Por qué perfilar el código?
\n\nImagina que tu servidor procesa 100 solicitudes por segundo, pero necesitas 500. Podrías optimizar todo, pero eso te llevaría semanas. O puedes encontrar una función que consume el 80% del tiempo y corregirla en una hora. El perfilado es exactamente lo que muestra dónde se gasta el tiempo.
\n\nConsideremos los escenarios principales donde el perfilado es necesario:
\n- \n
- Alta latencia en respuestas de API \n
- Fugas de memoria (memory leaks) \n
- Alta carga de CPU sin razón aparente \n
- Procesamiento lento de grandes conjuntos de datos \n
Herramienta 1: Perfilador integrado de Node.js (--prof)
\n\nLa forma más sencilla es ejecutar Node.js con la bandera --prof. Esto recopila estadísticas de ejecución y las guarda en un archivo isolate-*.log.
Creemos un script de prueba que contenga un cuello de botella:
\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\nEjecutemos el perfilado:
\nnode --prof slow.js\n\nDespués de la ejecución, aparecerá un archivo isolate-0x...-v8.log. Para leerlo, usamos la utilidad node --prof-process:
node --prof-process isolate-0x...-v8.log > profile.txt\n\nAbramos profile.txt y veremos una tabla con porcentajes de tiempo:
[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 el 50% del tiempo se va en la función heavyComputation. Ahora sabemos qué optimizar primero.
Herramienta 2: Chrome DevTools (inspector)
\n\nPara un análisis más visual, conectemos Chrome DevTools. Ejecutemos Node.js con la bandera --inspect:
node --inspect slow.js\n\nAbramos Chrome y vayamos a chrome://inspect. Hagamos clic en «Open dedicated DevTools for Node». Pestaña «Profiler» → «Start» → ejecute el script → «Stop».
Verá un flamegraph — un gráfico donde el ancho del bloque muestra el tiempo de ejecución. Cuanto más ancho el bloque, más «caliente» es la función. Nuestro heavyComputation será un rectángulo enorme.
Consejo: Use el filtro «Focus selected» en la función para ver sus llamadas internas. Por ejemplo, notará que Math.sqrt se llama 10 millones de veces — quizás valga la pena calcular las raíces de antemano.
Herramienta 3: perf_hooks — mediciones precisas en el código
\n\nA veces es necesario medir una sección específica del código sin ejecutar herramientas externas. Para esto, usamos el módulo integrado perf_hooks.
Ejemplo de medición del tiempo de ejecución de una función:
\n\nconst { performance } = require('perf_hooks');\n\nfunction processData(items) {\n let result = [];\n for (let item of items) {\n // Simulación de procesamiento complejo\n for (let j = 0; j < 1000; j++) {\n result.push(item * j);\n }\n