Perfilado de código JavaScript: encontrando cuellos de botella para principiantes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Perfilado de código JavaScript: encontrando cuellos de botella

\n\n

Cuando 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\n

Imagina 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\n

Consideremos 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
\n\n

Herramienta 1: Perfilador integrado de Node.js (--prof)

\n\n

La 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.

\n\n

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\n

Ejecutemos el perfilado:

\n
node --prof slow.js
\n\n

Después de la ejecución, aparecerá un archivo isolate-0x...-v8.log. Para leerlo, usamos la utilidad node --prof-process:

\n
node --prof-process isolate-0x...-v8.log > profile.txt
\n\n

Abramos profile.txt y veremos una tabla con porcentajes de tiempo:

\n
[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\n

Vemos que el 50% del tiempo se va en la función heavyComputation. Ahora sabemos qué optimizar primero.

\n\n

Herramienta 2: Chrome DevTools (inspector)

\n\n

Para un análisis más visual, conectemos Chrome DevTools. Ejecutemos Node.js con la bandera --inspect:

\n
node --inspect slow.js
\n\n

Abramos Chrome y vayamos a chrome://inspect. Hagamos clic en «Open dedicated DevTools for Node». Pestaña «Profiler» → «Start» → ejecute el script → «Stop».

\n\n

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.

\n\n

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.

\n\n

Herramienta 3: perf_hooks — mediciones precisas en el código

\n\n

A 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.

\n\n

Ejemplo de medición del tiempo de ejecución de una función:

\n\n
const { 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                

Blogs

Book Recommendations