Профилирование TypeScript кода: находим узкие места на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Профилирование TypeScript кода: находим узкие места на практике

Когда ваш TypeScript проект начинает тормозить, а пользователи жалуются на задержки, наступает время профилирования. Профилирование — это не просто "посмотреть, что медленно", а системный подход к выявлению узких мест в производительности кода. В этой статье мы разберем практические методы профилирования TypeScript кода с реальными примерами и инструментами.

Зачем профилировать TypeScript код?

TypeScript компилируется в JavaScript, и все проблемы производительности, присущие JS, переходят в TS. Однако TypeScript добавляет свои нюансы: типизация, декораторы, дженерики могут создавать неожиданные накладные расходы. Профилирование помогает ответить на вопросы:

  • Какая функция выполняется дольше всего?
  • Где происходит утечка памяти?
  • Почему анимация тормозит на 60fps?
  • Какой алгоритм требует оптимизации?

Инструменты для профилирования TypeScript

Chrome DevTools (для браузерного кода)

Самый мощный инструмент для фронтенда. Откройте вкладку Performance, нажмите Record, выполните действие и остановите запись. Вы увидите flamegraph — визуализацию вызовов функций и времени их выполнения.

// Пример медленного кода для профилирования
function heavyCalculation(iterations: number): number {
    let result = 0;
    for (let i = 0; i < iterations; i++) {
        result += Math.sqrt(i) * Math.sin(i);
    }
    return result;
}

// Запустите в консоли браузера, затем профилируйте
heavyCalculation(1000000);

Node.js — встроенный профайлер

Для серверного кода используйте --inspect флаг и Chrome DevTools, или встроенный --prof для генерации логов.

// server.ts
import 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);
// Запуск: node --prof dist/server.js

Benchmark.js для точных замеров

Когда нужно сравнить две реализации, используйте библиотеку benchmark.

import Benchmark from 'benchmark';

const suite = new Benchmark.Suite();

// Медленный вариант
function slowSearch(arr: number[], target: number): boolean {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) return true;
    }
    return false;
}

// Быстрый вариант с Set
function 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 });

Практические примеры поиска узких мест

Пример 1: Утечка памяти через замыкания

Типичная проблема в React-приложениях — замыкания, удерживающие ссылки на большие объекты.

// Проблемный код
function createHeavyComponent() {
    const hugeData = new Array(1000000).fill('data');
    
    return {
        getData: () => hugeData, // замыкание удерживает hugeData
        updateData: (newData: string) => {
            // мутация, но ссылка остается
            hugeData.push(newData);
        }
    };
}

const component = createHeavyComponent();
// hugeData не будет собран GC, пока существует component

Решение: Используйте WeakMap или явно обнуляйте ссылки.

function createOptimizedComponent() {
    let hugeData: string[] | null = new Array(1000000).fill('data');
    
    return {
        getData: () => hugeData,
        cleanup: () => {
            hugeData = null; // разрешаем GC
        }
    };
}

Пример 2: Медленные циклы с дженериками

TypeScript дженерики на уровне типов не влияют на производительность, но их неправильное использование может привести к неэффективному коду.

// Медленный вариант с рекурсией
function deepFlatten<T>(arr: (T | T[])[]): T[] {
    let result: T[] = [];
    for (const item of arr) {
        if (Array.isArray(item)) {
            result = result.concat(deepFlatten(item));
        } else {
            result.push(item);
        }
    }
    return result;
}

// Быстрый вариант с итеративным подходом
function fastFlatten<T>(arr: (T | T[])[]): T[] {
    const result: T[] = [];
    const stack = [...arr];
    
    while (stack.length > 0) {
        const item = stack.pop()!;
        if (Array.isArray(item)) {
            stack.push(...item);
        } else {
            result.push(item);
        }
    }
    return result;
}

// Замер производительности
const nestedArray = [1, [2, [3, [4, 5]]], 6];
console.time('deepFlatten');
for (let i = 0; i < 10000; i++) deepFlatten(nestedArray);
console.timeEnd('deepFlatten'); // ~45ms

console.time('fastFlatten');
for (let i = 0; i < 10000; i++) fastFlatten(nestedArray);
console.timeEnd('fastFlatten'); // ~8ms

Пример 3: Профилирование асинхронного кода

Асинхронные операции часто скрывают узкие места из-за неправильного использования Promise.all или await.

// Медленный последовательный запрос
async function fetchUsersSequential(ids: number[]): Promise<User[]> {
    const users: User[] = [];
    for (const id of ids) {
        const user = await fetch(`/api/users/${id}`).then(r => r.json());
        users.push(user);
    }
    return users;
}

// Быстрый параллельный запрос
async function fetchUsersParallel(ids: number[]): Promise<User[]> {
    const promises = ids.map(id => 
        fetch(`/api/users/${id}`).then(r => r.json())
    );
    return Promise.all(promises);
}

// Профилируем с помощью performance.now()
async function profile() {
    const ids = [1, 2, 3, 4, 5];
    
    const start1 = performance.now();                

Блоги

Рекомендаций по книгам