Skip to content

Производительность

Тёплый вызов .get() - это один Map.get(key) и прямой new Ctor(...): без reflection, таблиц метаданных и proxy на пути. Цифры в бенчмарке ниже появляются из конкретных решений в рантайме, а не из отдельного fast mode, который нужно включать вручную:

РешениеЭффект
Явные регистрацииСборка контейнера делает плоский Map.set на сервис. Нет побочных эффектов декораторов, парсеров имён конструкторов и таблиц метаданных.
Закешированные singleton- и scoped-сервисыТёплый вызов .get() читает cache.get(key) до проверок циклов и времени жизни. Запасной cache.has(key) нужен только для явно зарегистрированного undefined.
Прямые вызовы конструкторовКлассы с 0-7 зависимостями идут по прямому пути new Ctor(...). Конструкторы с большим числом аргументов используют Reflect.construct.
Асинхронные фабрикиФабричный Promise кешируется как есть, поэтому параллельные вызовы делят одну начатую инициализацию, а .get() остаётся синхронным.
Граница strict modestrict: true ловит циклы и утечки времени жизни. strict: false убирает эти проверки для заранее проверенных горячих transient-графов.

Результаты бенчмарков

Набор бенчмарков

Набор бенчмарков сравнивает InferDI с InversifyJS v8, Awilix v13 в режимах PROXY и CLASSIC, TSyringe v4, TypeDI v0.10 и Typed Inject v5.

Все значения указаны в операциях в секунду на Node 22. Чем выше, тем лучше.

СценарийInferDITyped InjectAwilix (PROXY)Awilix (CLASSIC)InversifyJSTSyringeTypeDI
1. Горячий singleton (тёплый кеш)14.2 M7.0 M7.2 M6.9 M6.3 M6.2 M6.4 M
2. Transient-сервис (новый экземпляр на вызов)8.4 M4.3 M3.4 M2.9 M3.4 M2.4 M1.6 M
3. Глубокий граф (10 уровней, всё transient)1.85 M1.28 M701 k739 k750 k601 k214 k
4a. Широкий граф (4 зависимости, корень transient)7.3 M3.2 M2.2 M2.3 M2.3 M1.6 M1.1 M
4b. Широкий граф (10 зависимостей, корень transient)3.5 M2.6 M1.2 M1.3 M1.6 M1.0 M437 k
5. Сборка контейнера и первый вызов400 k228 k10 k8 k13 k202 k272 k
6. Жизненный цикл scope (создание, resolve, очистка)2.66 M2.39 M492 k413 k28 k1.08 M637 k
7. Ленивый resolve (обёртка с отложенным доступом)12.1 M7.0 M5.5 M4.7 M4.2 M4.0 M2.8 M

Что показывают цифры

  • Закешированный singleton-resolve примерно в 2 раза быстрее ближайшей альтернативы в этом наборе.
  • Сборка контейнера вместе с первым вызовом выигрывает за счёт плоской регистрации. InferDI регистрирует граф с нуля; библиотеки на декораторах уже оплатили часть этой работы при загрузке модулей.
  • Сценарии с широким графом показывают пользу развёрнутых вызовов по числу аргументов. До семи зависимостей V8 может заинлайнить прямой вызов конструктора. На десяти зависимостях InferDI переходит на Reflect.construct и всё равно лидирует среди сравниваемых библиотек.
  • Жизненный цикл scope включает создание scope, resolve и очистку. Сценарий 6 выполняет dispose на каждой итерации, поэтому измеряет владение scope, а не только получение значения.
  • Typed Inject остаётся ближайшей альтернативой. Его граф, известный на этапе компиляции, хорошо держится на глубоких графах и scoped-потоках.

Быстрый режим

new Container({ strict: false }) убирает runtime-учёт циклов, отслеживание singleton-стека и try/finally вокруг защищённого пути resolve. В README пакета показано ускорение примерно на 30% для локальных transient-вызовов на плоском transient-графе. Закешированные singleton- и scoped-вызовы не меняются, потому что возвращаются до этих проверок.

Включайте быстрый режим только после тестов, которые прогнали граф в стандартном strict mode. TypeScript не видит singleton-циклы, transient-циклы, динамические ключи, as-касты и фабрики, захватившие внешний контейнер с более широким типом.

Детали горячего пути

Symbol-ключи могут помочь в плотных циклах resolve, потому что Map сравнивает их по идентичности. Строковым ключам нужен хеш, а при коллизии - посимвольное сравнение. В большинстве приложений разница не измеряется, поэтому переходите на symbol-ключи только после сигнала профилировщика.

Запуск локально

bash
cd benchmarks
pnpm install --frozen-lockfile
pnpm run precondition
pnpm run bench

Рабочее пространство бенчмарков изолировано от корневого workspace pnpm и имеет собственный lockfile. Методология и фикстуры описаны в benchmarks/README.md.