Perfilado de código TypeScript: encontrando cuellos de botella en la práctica
Cuando su proyecto TypeScript comienza a ralentizarse y los usuarios se quejan de retrasos, llega el momento del perfilado. El perfilado no es solo "mirar qué es lento", sino un enfoque sistemático para identificar cuellos de botella en el rendimiento del código. En este artículo, analizaremos métodos prácticos de perfilado de código TypeScript con ejemplos y herramientas reales.
¿Por qué perfilar código TypeScript?
TypeScript se compila a JavaScript, y todos los problemas de rendimiento inherentes a JS se trasladan a TS. Sin embargo, TypeScript añade sus propios matices: la tipificación, los decoradores y los genéricos pueden crear gastos generales inesperados. El perfilado ayuda a responder preguntas como:
- ¿Qué función se ejecuta durante más tiempo?
- ¿Dónde se produce una fuga de memoria?
- ¿Por qué la animación se ralentiza a 60fps?
- ¿Qué algoritmo necesita optimización?
Herramientas para perfilar TypeScript
Chrome DevTools (para código de navegador)
La herramienta más potente para el frontend. Abra la pestaña Performance, presione Record, realice la acción y detenga la grabación. Verá un flamegraph: una visualización de las llamadas a funciones y su tiempo de ejecución.
// Ejemplo de código lento para perfilarfunction heavyCalculation(iterations: number): number { let result = 0; for (let i = 0; i < iterations; i++) { result += Math.sqrt(i) * Math.sin(i); } return result;}
// Ejecútelo en la consola del navegador, luego perfíleloheavyCalculation(1000000);Node.js — perfilador integrado
Para código de servidor, use el flag --inspect y Chrome DevTools, o el integrado --prof para generar registros.
// server.tsimport express from 'express';
const app = express();
app.get('/heavy', (req, res) => { const data = processData(10000); res.json({ result: data });});
function processData(size: number): number[] { const arr: number[] = []; for (let i = 0; i < size; i++) { arr.push(i * Math.random()); } return arr.sort((a, b) => a - b);}
app.listen(3000);// Ejecución: node --prof dist/server.jsBenchmark.js para mediciones precisas
Cuando necesite comparar dos implementaciones, use la librería benchmark.
import Benchmark from 'benchmark';
const suite = new Benchmark.Suite();
// Variante lentafunction slowSearch(arr: number[], target: number): boolean { for (let i = 0; i < arr.length; i++) { if (arr[i] === target) return true; } return false;}
// Variante rápida con Setfunction fastSearch(arr: number[], target: number): boolean { const set = new Set(arr); return set.has(target);}
const testArray = Array.from({ length: 10000 }, (_, i) => i);
suite .add('slowSearch', () => slowSearch(testArray, 9999)) .add('fastSearch', () => fastSearch(testArray, 9999)) .on('cycle', (event: any) => console.log(String(event.target))) .on('complete', function(this: any) { console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true });Ejemplos prácticos de búsqueda de cuellos de botella
Ejemplo 1: Fuga de memoria mediante closures
Un problema típico en aplicaciones React son los closures que retienen referencias a objetos grandes.
// Código problemáticofunction createHeavyComponent() { const hugeData = new Array(1000000).fill('data'); return { getData: () => hugeData, // el closure retiene hugeData updateData: (newData: string) => { // mutación, pero la referencia permanece hugeData.push(newData); } };}
const component = createHeavyComponent();// hugeData no será recolectado por el GC mientras exista component<