Perfilado de código TypeScript: encontrando cuellos de botella en la práctica
\nCada desarrollador se ha enfrentado a la situación en la que la aplicación comienza a ralentizarse y la causa no es evidente. En el mundo de TypeScript, donde el código se transpila a JavaScript, los cuellos de botella pueden surgir tanto en la fase de ejecución como debido a las características de la propia transpilación. En este artículo analizaremos métodos prácticos de perfilado de código TypeScript que ayudarán a identificar las áreas problemáticas y mejorar el rendimiento de su aplicación.
\n\n¿Por qué es importante el perfilado de TypeScript?
\nTypeScript añade tipado estático, pero en tiempo de ejecución se ejecuta JavaScript normal. Esto significa que los cuellos de botella pueden estar relacionados no solo con los algoritmos, sino también con la forma en que se compila TypeScript. Por ejemplo, el uso excesivo de decoradores o genéricos complejos puede generar código no óptimo. El perfilado ayuda a responder preguntas como:
\n- \n
- ¿Qué funciones tardan más en ejecutarse? \n
- ¿Cuánta memoria consume la aplicación? \n
- ¿Dónde ocurren recolecciones de basura frecuentes? \n
- ¿Qué partes del código bloquean el hilo principal? \n
Sin perfilado, corre el riesgo de optimizar las partes equivocadas, perdiendo tiempo en microoptimizaciones que no darán una mejora notable.
\n\nHerramientas para el perfilado de TypeScript
\nPara el perfilado de código TypeScript utilizaremos tres herramientas principales que cubren la mayoría de los escenarios: desde aplicaciones de navegador hasta soluciones de servidor en Node.js.
\n\n1. Chrome DevTools para aplicaciones de navegador
\nLa herramienta más accesible y potente. Abra la pestaña Performance en Chrome DevTools, inicie la grabación y realice la acción deseada en la aplicación. Al detenerse, obtendrá un flame chart — una representación visual de las llamadas a funciones. Así es como se ve en la práctica:
\n// Ejemplo de código lento en TypeScript\nfunction processData(data: number[]): number[] {\n const result: number[] = [];\n for (let i = 0; i < data.length; i++) {\n // Posible cuello de botella: llamadas frecuentes\n result.push(data[i] * Math.sqrt(data[i]));\n }\n return result;\n}\n\n// Uso\nconst largeArray = Array.from({ length: 100000 }, (_, i) => i);\nconsole.time('processData');\nprocessData(largeArray);\nconsole.timeEnd('processData'); // ~15ms\n\nEn Chrome DevTools verá que la función processData ocupa un tiempo significativo, y dentro de ella, muchas llamadas a Math.sqrt. Esto es una pista de que se puede reemplazar la iteración con map o vectorizar los cálculos.
2. Node.js profiler para aplicaciones de servidor
\nNode.js tiene un perfilador integrado. Ejecute la aplicación con la bandera --prof:
node --prof dist/app.js\n\nDespués de finalizar la ejecución, se generará un archivo isolate-*.log. Conviértalo a un formato legible:
node --prof-process isolate-*.log > processed.txt\n\nUn ejemplo de problema típico son las asignaciones frecuentes en bucles:
\n// Mal ejemplo: creación de objetos en un bucle\ninterface User {\n id: number;\n name: string;\n}\n\nfunction createUsers(ids: number[]): User[] {\n const users: User[] = [];\n for (const id of ids) {\n users.push({ id, name: `User_${id}` }); // Nueva asignación\n }\n return users;\n}\n\nEl perfilador mostrará que la función createUsers crea muchos objetos temporales, lo que aumenta la carga del recolector de basura. Optimización: reservar memoria de antemano o usar un pool de objetos.
3. Benchmark.js para microbenchmarks
\nCuando necesite comparar el rendimiento de dos implementaciones, use la biblioteca benchmark.js. Instálela a través de npm:
npm install benchmark @types/benchmark\n\nEjemplo de comparación de dos formas de filtrar un array:
\nimport Benc