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

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

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

Начать курс

Введение: почему профилирование TypeScript важно для производительности

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

В этой статье мы разберем, как профилировать TypeScript код с помощью встроенных инструментов браузера и Node.js. Вы узнаете, как находить медленные функции, анализировать стек вызовов и оптимизировать критические участки.

Статья рассчитана на начинающих разработчиков, которые уже знакомы с основами TypeScript и хотят улучшить производительность своих приложений.

Основные понятия профилирования

Профилирование — это процесс сбора данных о том, как программа выполняется: сколько времени занимают функции, как часто они вызываются, какие ресурсы потребляются.

В контексте TypeScript важно понимать несколько ключевых метрик:

  • Время выполнения — сколько миллисекунд занимает каждая функция.
  • Количество вызовов — как часто вызывается функция.
  • Память — сколько памяти выделяется и освобождается.
  • Стек вызовов — последовательность вызова функций.

Для профилирования TypeScript кода можно использовать как инструменты браузера (Chrome DevTools), так и утилиты Node.js (например, --inspect или clinic.js).

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

Chrome DevTools для клиентского кода

Если ваше TypeScript приложение работает в браузере, лучший инструмент — встроенные Chrome DevTools. Откройте вкладку Performance и нажмите кнопку записи. Выполните действие на странице, остановите запись и получите таймлайн.

Пример анализа: вы видите, что функция processData занимает 300 мс. Это сигнал для оптимизации.

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

Для серверного TypeScript кода (Node.js) используйте флаг --inspect или --prof. Например:

node --inspect dist/app.js

Затем откройте chrome://inspect в браузере и подключитесь к процессу. Вы получите те же возможности DevTools, но для серверного кода.

clinic.js — простая утилита для Node.js

Установите clinic.js глобально:

npm install -g clinic

Запустите профилирование:

clinic doctor -- node dist/app.js

Утилита покажет наглядный отчет с рекомендациями.

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

Пример 1: Медленная сортировка массива

Предположим, у вас есть функция сортировки большого массива объектов:

interface User {
    id: number;
    name: string;
    age: number;
}

function sortUsers(users: User[]): User[] {
    return users.sort((a, b) => {
        // Медленное сравнение строк
        const nameA = a.name.toLowerCase();
        const nameB = b.name.toLowerCase();
        if (nameA < nameB) return -1;
        if (nameA > nameB) return 1;
        return 0;
    });
}

При профилировании вы заметите, что toLowerCase() вызывается для каждого сравнения, а их может быть O(n log n). Узкое место — повторное преобразование строк.

Оптимизация: предварительно преобразуйте строки:

function sortUsersOptimized(users: User[]): User[] {
    return users
        .map(user => ({
            ...user,
            _lowerName: user.name.toLowerCase()
        }))
        .sort((a, b) => {
            if (a._lowerName < b._lowerName) return -1;
            if (a._lowerName > b._lowerName) return 1;
            return 0;
        })
        .map(({ _lowerName, ...user }) => user);
}

Пример 2: Частое создание объектов

Рассмотрим функцию, которая обрабатывает массив чисел:

function processNumbers(nums: number[]): number[] {
    const result: number[] = [];
    for (let i = 0; i < nums.length; i++) {
        // Создание нового объекта на каждой итерации
        const obj = { value: nums[i], doubled: nums[i] * 2 };
        result.push(obj.doubled);
    }
    return result;
}

Профилирование покажет высокую нагрузку на сборщик мусора из-за создания множества объектов. Узкое место — выделение памяти в цикле.

Оптимизация: избегайте создания объектов в горячих циклах:

function processNumbersOptimized(nums: number[]): number[] {
    const result: number[] = new Array(nums.length);
    for (let i = 0; i < nums.length; i++) {
        result[i] = nums[i] * 2;
    }
    return result;
}

Пример 3: Сложные рекурсивные вычисления

Рекурсивные функции без мемоизации — классическое узкое место:

function fibonacci(n: number): number {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Профилирование покажет экспоненциальный рост времени выполнения. Решение — добавить кэширование:

const memo = new Map<number, number>();

function fibonacciMemo(n: number): number {
    if (n <= 1) return n;
    if (memo.has(n)) return memo.get(n)!;
    const result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
    memo.set(n, result);
    return result;
}

Как интерпретировать результаты профилирования

После запуска профилирования вы увидите таблицу с функциями и их временем. Обращайте внимание на:

  • Self time — время, потраченное непосредственно в функции (без учета вызовов дочерних функций).
  • Total time — общее время с учетом всех вложенных вызовов.
  • Call count — количество вызовов. Если функция вызывается миллионы раз, даже микросекунда на вызов дает секунды.

В Chrome DevTools также доступен Flame Chart — графическое представление стека вызовов. Ш

Блоги

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