パフォーマンス
ウォームな解決は、1 回の Map.get(key) に続く直接的な new Ctor(...) です — リフレクションも、メタデータテーブルも、間に挟まるプロキシもありません。以下のベンチマーク数値は、オプトインで有効にする特別な高速モードからではなく、いくつかの具体的なランタイム上の選択から導かれたものです。
| ランタイム上の選択 | 効果 |
|---|---|
| 明示的な登録 | コンテナのビルドはサービスごとのフラットな Map.set です。準備すべきデコレーターの副作用も、コンストラクター名のパーサーも、メタデータテーブルもありません。 |
| シングルトンおよびスコープドサービスのキャッシュ | ウォームな解決は、サイクルとライフタイムの管理処理が実行される前に cache.get(key) から読み取ります。cache.has(key) のフォールバックは明示的な undefined 値のためだけに存在します。 |
| 直接的なコンストラクター呼び出し | 依存が 0〜7 個のクラスは直接的な new Ctor(...) の経路を使用します。より大きなコンストラクターは Reflect.construct にフォールバックします。 |
| 非同期ファクトリー | ファクトリーの Promise はそのままキャッシュされるため、.get() が同期のままで、並行する呼び出し元は進行中の初期化を 1 つ共有します。 |
| strict モードの境界 | strict: true はサイクルとライフタイムのリークを検出します。strict: false は、監査済みのホットなトランジェントグラフに対してその管理処理を取り除きます。 |

ベンチマークスイート
リポジトリのベンチマークスイートは、InferDI を InversifyJS v8、PROXY および CLASSIC モードの Awilix v13、TSyringe v4、TypeDI v0.10、そして Typed Inject v5 と比較します。
すべての数値は Node 22 における 1 秒あたりの操作回数です。高いほど良好です。
| シナリオ | InferDI | Typed Inject | Awilix (PROXY) | Awilix (CLASSIC) | InversifyJS | TSyringe | TypeDI |
|---|---|---|---|---|---|---|---|
| 1. ホットなシングルトンの解決(ウォームキャッシュ) | 14.2 M | 7.0 M | 7.2 M | 6.9 M | 6.3 M | 6.2 M | 6.4 M |
| 2. トランジェントの解決(呼び出しごとに新しいインスタンス) | 8.4 M | 4.3 M | 3.4 M | 2.9 M | 3.4 M | 2.4 M | 1.6 M |
| 3. 深いグラフ(10 階層、すべてトランジェント) | 1.85 M | 1.28 M | 701 k | 739 k | 750 k | 601 k | 214 k |
| 4a. 広いグラフ(4 依存、ルートがトランジェント) | 7.3 M | 3.2 M | 2.2 M | 2.3 M | 2.3 M | 1.6 M | 1.1 M |
| 4b. 広いグラフ(10 依存、ルートがトランジェント) | 3.5 M | 2.6 M | 1.2 M | 1.3 M | 1.6 M | 1.0 M | 437 k |
| 5. コンテナのビルド + 最初の解決 | 400 k | 228 k | 10 k | 8 k | 13 k | 202 k | 272 k |
| 6. スコープドのライフサイクル(作成 + 解決 + クリーンアップ) | 2.66 M | 2.39 M | 492 k | 413 k | 28 k | 1.08 M | 637 k |
| 7. 遅延解決(遅延ラッパー) | 12.1 M | 7.0 M | 5.5 M | 4.7 M | 4.2 M | 4.0 M | 2.8 M |
数値が示すもの
- キャッシュされたシングルトンの解決は、このスイートで最も近いベースラインの約 2 倍の速さで実行されます。
- コンテナのビルドと最初の解決はフラットな登録に有利です。InferDI はグラフをゼロから登録します。デコレーターベースのライブラリは、モジュールの評価中に登録作業の一部をすでに支払い済みです。
- 広いグラフのシナリオは、アリティのアンローリングがなぜ重要かを示しています。7 個までの依存であれば、V8 は直接的なコンストラクター呼び出しをインライン化できます。10 個の依存になると、InferDI は
Reflect.constructにフォールバックしますが、それでも記載されているベースラインを上回ります。 - スコープドのライフサイクルには、スコープの作成、解決、そしてクリーンアップが含まれます。シナリオ 6 は反復のたびに破棄処理を含むため、解決単独ではなくスコープの所有を測定します。
- Typed Inject は InferDI を除いて最も近いベースラインです。コンパイル時に判明するグラフのおかげで、深いグラフとスコープドのフローで競争力を保っています。
高速モード
new Container({ strict: false }) は、ランタイムのサイクル管理、シングルトンスタックの追跡、そしてガードされた解決経路を囲む try/finally を取り除きます。パッケージの README では、フラットなトランジェントグラフでローカルのトランジェント解決が約 30% 高速になると報告されています。キャッシュされたシングルトンおよびスコープドの解決は、それらのガードが実行される前に値を返すため変化しません。
高速モードは、デフォルトの strict モードでテストがグラフを十分に実行した後にのみ使用してください。TypeScript はシングルトンのサイクル、トランジェントのサイクル、動的キー、as キャスト、あるいはより広い外側のコンテナをクロージャに取り込むファクトリーを見ることができません。
ホットパスの細かな詳細
シンボルキーは、Map がそれらを同一性で比較するため、タイトな解決ループで役立つことがあります。文字列キーはハッシュ化が必要で、衝突時には文字単位の比較が必要です。ほとんどのアプリケーションでは差を計測できないため、シンボルキーはプロファイラー主導の変更として扱ってください。
ローカルで再現する
cd benchmarks
pnpm install --frozen-lockfile
pnpm run precondition
pnpm run benchベンチマークのワークスペースは意図的にルートの pnpm ワークスペースから分離されており、独自のロックファイルを持っています。方法論、公平性に関する注記、そしてフィクスチャのソースについては benchmarks/README.md を参照してください。
