diff --git a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart index eea5849..55159a0 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart @@ -25,7 +25,11 @@ class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { bindingMode: mode, scenario: UniversalScenario.asyncChain, )); + } + + Future prewarm() async { await di.waitForAsyncReady(); + await run(); } @override diff --git a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart index 7f7b506..7777fb7 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -50,7 +50,14 @@ class UniversalChainBenchmark extends BenchmarkBase { } @override - void teardown() => _di.teardown(); + void teardown() { + _childDi?.teardown(); + _di.teardown(); + } + + void prewarm() { + run(); + } @override void run() { @@ -59,11 +66,7 @@ class UniversalChainBenchmark extends BenchmarkBase { _di.resolve(); break; case UniversalScenario.named: - if (_di.runtimeType.toString().contains('GetItAdapter')) { - _di.resolve(named: 'impl2'); - } else { - _di.resolve(named: 'impl2'); - } + _di.resolve(named: 'impl2'); break; case UniversalScenario.chain: final serviceName = '${chainCount}_$nestingDepth'; diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index 793e8e7..d8832b8 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -31,184 +31,205 @@ class BenchmarkCliRunner { Future run(List args) async { final config = parseBenchmarkCli(args); final results = >[]; - for (final bench in config.benchesToRun) { - final scenario = toScenario(bench); - final mode = toMode(bench); - for (final c in config.chainCounts) { - for (final d in config.nestDepths) { - BenchmarkResult benchResult; - if (config.di == 'getit') { - final di = GetItAdapter(); - if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: config.warmups, - repeats: config.repeats, - ); + // DI implementations that do not support async scenarios + const asyncUnsupported = {'kiwi', 'yx_scope'}; + for (final phase in config.phases) { + for (final bench in config.benchesToRun) { + final scenario = toScenario(bench); + final mode = toMode(bench); + if (asyncUnsupported.contains(config.di) && + scenario == UniversalScenario.asyncChain) { + continue; // Skip async benchmarks for DI that does not support them + } + for (final c in config.chainCounts) { + for (final d in config.nestDepths) { + BenchmarkResult benchResult; + if (config.di == 'getit') { + final di = GetItAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } else { + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } + } else if (config.di == 'kiwi') { + final di = KiwiAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } else { + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } + } else if (config.di == 'riverpod') { + final di = RiverpodAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = UniversalChainAsyncBenchmark< + Map>>( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } else { + final benchSync = UniversalChainBenchmark< + Map>>( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } + } else if (config.di == 'yx_scope') { + final di = YxScopeAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = + UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } else { + final benchSync = + UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } } else { - final benchSync = UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: config.warmups, - repeats: config.repeats, - ); - } - } else if (config.di == 'kiwi') { - final di = KiwiAdapter(); - if (scenario == UniversalScenario.asyncChain) { - // UnsupportedError будет выброшен адаптером, но если дойдёт — вызывать async benchmark - final benchAsync = UniversalChainAsyncBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: config.warmups, - repeats: config.repeats, - ); - } else { - final benchSync = UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: config.warmups, - repeats: config.repeats, - ); - } - } else if (config.di == 'riverpod') { - final di = RiverpodAdapter(); - if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark< - Map>>( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: config.warmups, - repeats: config.repeats, - ); - } else { - final benchSync = UniversalChainBenchmark< - Map>>( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: config.warmups, - repeats: config.repeats, - ); - } - } else if (config.di == 'yx_scope') { - final di = YxScopeAdapter(); - if (scenario == UniversalScenario.asyncChain) { - final benchAsync = - UniversalChainAsyncBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: config.warmups, - repeats: config.repeats, - ); - } else { - final benchSync = - UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: config.warmups, - repeats: config.repeats, - ); - } - } else { - final di = CherrypickDIAdapter(); - if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: config.warmups, - repeats: config.repeats, - ); - } else { - final benchSync = UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: config.warmups, - repeats: config.repeats, - ); + final di = CherrypickDIAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } else { + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: config.warmups, + repeats: config.repeats, + phase: phase, + ); + } } + final timings = benchResult.timings; + if (timings.isEmpty) continue; // skip failed scenarios + timings.sort(); + final count = timings.length; + final mean = timings.reduce((a, b) => a + b) / count; + final median = count.isOdd + ? timings[count ~/ 2] + : (timings[count ~/ 2 - 1] + timings[count ~/ 2]) / 2; + final minVal = timings.first; + final maxVal = timings.last; + final stddev = sqrt(timings + .map((x) => pow(x - mean, 2)) + .reduce((a, b) => a + b) / + count); + results.add({ + 'benchmark': 'Universal_$bench', + 'phase': phase.name, + 'chainCount': c, + 'nestingDepth': d, + 'mean_us': mean.toStringAsFixed(2), + 'median_us': median.toStringAsFixed(2), + 'stddev_us': stddev.toStringAsFixed(2), + 'min_us': minVal.toStringAsFixed(2), + 'max_us': maxVal.toStringAsFixed(2), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.toStringAsFixed(2)).toList(), + 'memory_diff_kb': benchResult.memoryDiffKb, + 'delta_peak_kb': benchResult.deltaPeakKb, + 'peak_rss_kb': benchResult.peakRssKb, + }); } - final timings = benchResult.timings; - timings.sort(); - var mean = timings.reduce((a, b) => a + b) / timings.length; - var median = timings[timings.length ~/ 2]; - var minVal = timings.first; - var maxVal = timings.last; - var stddev = timings.isEmpty - ? 0 - : sqrt( - timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / - timings.length); - results.add({ - 'benchmark': 'Universal_$bench', - 'chainCount': c, - 'nestingDepth': d, - 'mean_us': mean.toStringAsFixed(2), - 'median_us': median.toStringAsFixed(2), - 'stddev_us': stddev.toStringAsFixed(2), - 'min_us': minVal.toStringAsFixed(2), - 'max_us': maxVal.toStringAsFixed(2), - 'trials': timings.length, - 'timings_us': timings.map((t) => t.toStringAsFixed(2)).toList(), - 'memory_diff_kb': benchResult.memoryDiffKb, - 'delta_peak_kb': benchResult.deltaPeakKb, - 'peak_rss_kb': benchResult.peakRssKb, - }); } } } diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart index 0b4b911..a00810a 100644 --- a/benchmark_di/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -6,12 +6,18 @@ import 'package:benchmark_di/scenarios/universal_scenario.dart'; /// Enum describing all supported Universal DI benchmark types. enum UniversalBenchmark { - /// Simple singleton registration benchmark + /// Simple singleton registration benchmark (eager, where supported) registerSingleton, - /// Chain of singleton dependencies + /// Simple lazy singleton registration benchmark + registerLazySingleton, + + /// Chain of eager singleton dependencies chainSingleton, + /// Chain of lazy singleton dependencies + chainLazySingleton, + /// Chain using factories chainFactory, @@ -25,12 +31,19 @@ enum UniversalBenchmark { override, } +enum ResolvePhase { + firstResolve, + steadyStateResolve, +} + /// Maps [UniversalBenchmark] to the scenario enum for DI chains. UniversalScenario toScenario(UniversalBenchmark b) { switch (b) { case UniversalBenchmark.registerSingleton: + case UniversalBenchmark.registerLazySingleton: return UniversalScenario.register; case UniversalBenchmark.chainSingleton: + case UniversalBenchmark.chainLazySingleton: return UniversalScenario.chain; case UniversalBenchmark.chainFactory: return UniversalScenario.chain; @@ -43,21 +56,21 @@ UniversalScenario toScenario(UniversalBenchmark b) { } } -/// Maps benchmark to registration mode (singleton/factory/async). +/// Maps benchmark to registration mode (singleton/lazySingleton/factory/async). UniversalBindingMode toMode(UniversalBenchmark b) { switch (b) { case UniversalBenchmark.registerSingleton: - return UniversalBindingMode.singletonStrategy; case UniversalBenchmark.chainSingleton: + case UniversalBenchmark.named: + case UniversalBenchmark.override: return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.registerLazySingleton: + case UniversalBenchmark.chainLazySingleton: + return UniversalBindingMode.lazySingletonStrategy; case UniversalBenchmark.chainFactory: return UniversalBindingMode.factoryStrategy; case UniversalBenchmark.chainAsync: return UniversalBindingMode.asyncStrategy; - case UniversalBenchmark.named: - return UniversalBindingMode.singletonStrategy; - case UniversalBenchmark.override: - return UniversalBindingMode.singletonStrategy; } } @@ -98,6 +111,10 @@ class BenchmarkCliConfig { /// Name of DI implementation ("cherrypick" or "getit") final String di; + + /// Which resolve phase(s) to measure. + final List phases; + BenchmarkCliConfig({ required this.benchesToRun, required this.chainCounts, @@ -106,6 +123,7 @@ class BenchmarkCliConfig { required this.warmups, required this.format, required this.di, + required this.phases, }); } @@ -119,6 +137,8 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { ..addOption('repeat', abbr: 'r', defaultsTo: '2') ..addOption('warmup', abbr: 'w', defaultsTo: '1') ..addOption('format', abbr: 'f', defaultsTo: 'pretty') + ..addOption('resolvePhase', + defaultsTo: 'all', help: 'Resolve phase: first, steady, or all') ..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod') @@ -128,12 +148,39 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { print(parser.usage); exit(0); } - final benchName = result['benchmark'] as String; - final isAll = benchName == 'all'; + final benchNameInput = result['benchmark'] as String; + final isAll = benchNameInput.trim() == 'all'; final allBenches = UniversalBenchmark.values; + + String normalizeBenchName(String name) { + final n = name.trim().toLowerCase(); + return switch (n) { + 'register' || 'registersingleton' || 'registereager' => 'registerSingleton', + 'registerlazy' || 'registerlazysingleton' || 'registerlazysingle' => 'registerLazySingleton', + 'chain' || 'chainsingleton' || 'chaineager' => 'chainSingleton', + 'chainlazy' || 'chainlazysingleton' || 'lazysingleton' => 'chainLazySingleton', + 'chainfactory' || 'factory' => 'chainFactory', + 'async' || 'asyncchain' || 'chainasync' => 'chainAsync', + 'named' => 'named', + 'override' => 'override', + _ => n, + }; + } + final benchesToRun = isAll ? allBenches - : [parseEnum(benchName, allBenches, UniversalBenchmark.chainSingleton)]; + : benchNameInput + .split(',') + .map((n) => parseEnum(normalizeBenchName(n), allBenches, + UniversalBenchmark.chainSingleton)) + .toSet() + .toList(); + final phaseName = (result['resolvePhase'] as String).toLowerCase(); + final phases = switch (phaseName) { + 'first' => [ResolvePhase.firstResolve], + 'steady' => [ResolvePhase.steadyStateResolve], + _ => ResolvePhase.values, + }; return BenchmarkCliConfig( benchesToRun: benchesToRun, chainCounts: parseIntList(result['chainCount'] as String), @@ -142,5 +189,6 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { warmups: int.tryParse(result['warmup'] as String? ?? "") ?? 1, format: result['format'] as String, di: result['di'] as String? ?? 'cherrypick', + phases: phases, ); } diff --git a/benchmark_di/lib/cli/report/csv_report.dart b/benchmark_di/lib/cli/report/csv_report.dart index e6662ef..dd2b1bc 100644 --- a/benchmark_di/lib/cli/report/csv_report.dart +++ b/benchmark_di/lib/cli/report/csv_report.dart @@ -6,6 +6,7 @@ class CsvReport extends ReportGenerator { @override final List keys = [ 'benchmark', + 'phase', 'chainCount', 'nestingDepth', 'mean_us', diff --git a/benchmark_di/lib/cli/report/markdown_report.dart b/benchmark_di/lib/cli/report/markdown_report.dart index 4a2d1cf..24e4a21 100644 --- a/benchmark_di/lib/cli/report/markdown_report.dart +++ b/benchmark_di/lib/cli/report/markdown_report.dart @@ -8,6 +8,7 @@ class MarkdownReport extends ReportGenerator { @override final List keys = [ 'benchmark', + 'phase', 'chainCount', 'nestingDepth', 'mean_us', @@ -24,7 +25,9 @@ class MarkdownReport extends ReportGenerator { /// Friendly display names for each benchmark type. static const nameMap = { 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', + 'Universal_UniversalBenchmark.registerLazySingleton': 'RegisterLazySingleton', 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', + 'Universal_UniversalBenchmark.chainLazySingleton': 'ChainLazySingleton', 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', 'Universal_UniversalBenchmark.named': 'Named', @@ -36,6 +39,7 @@ class MarkdownReport extends ReportGenerator { String render(List> rows) { final headers = [ 'Benchmark', + 'Phase', 'Chain Count', 'Depth', 'Mean (us)', @@ -52,6 +56,7 @@ class MarkdownReport extends ReportGenerator { final readableName = nameMap[r['benchmark']] ?? r['benchmark']; return [ readableName, + r['phase'], r['chainCount'], r['nestingDepth'], r['mean_us'], @@ -82,6 +87,7 @@ class MarkdownReport extends ReportGenerator { final legend = ''' > **Legend:** > `Benchmark` – Test name + > `Phase` – `firstResolve` or `steadyStateResolve` > `Chain Count` – Number of independent chains > `Depth` – Depth of each chain > `Mean (us)` – Average time per run (microseconds) diff --git a/benchmark_di/lib/cli/report/pretty_report.dart b/benchmark_di/lib/cli/report/pretty_report.dart index 3d46d46..4d8c9ce 100644 --- a/benchmark_di/lib/cli/report/pretty_report.dart +++ b/benchmark_di/lib/cli/report/pretty_report.dart @@ -8,6 +8,7 @@ class PrettyReport extends ReportGenerator { @override final List keys = [ 'benchmark', + 'phase', 'chainCount', 'nestingDepth', 'mean_us', @@ -24,7 +25,9 @@ class PrettyReport extends ReportGenerator { /// Mappings from internal benchmark IDs to display names. static const nameMap = { 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', + 'Universal_UniversalBenchmark.registerLazySingleton': 'RegisterLazySingleton', 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', + 'Universal_UniversalBenchmark.chainLazySingleton': 'ChainLazySingleton', 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', 'Universal_UniversalBenchmark.named': 'Named', @@ -36,6 +39,7 @@ class PrettyReport extends ReportGenerator { String render(List> rows) { final headers = [ 'Benchmark', + 'Phase', 'Chain Count', 'Depth', 'Mean (us)', @@ -53,6 +57,7 @@ class PrettyReport extends ReportGenerator { final readableName = nameMap[r['benchmark']] ?? r['benchmark']; return [ readableName, + r['phase'], r['chainCount'], r['nestingDepth'], r['mean_us'], diff --git a/benchmark_di/lib/cli/runner.dart b/benchmark_di/lib/cli/runner.dart index 2f5a376..e77fa7d 100644 --- a/benchmark_di/lib/cli/runner.dart +++ b/benchmark_di/lib/cli/runner.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:math'; import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; +import 'package:benchmark_di/cli/parser.dart'; /// Holds the results for a single benchmark execution. class BenchmarkResult { @@ -50,17 +51,24 @@ class BenchmarkRunner { required UniversalChainBenchmark benchmark, required int warmups, required int repeats, + required ResolvePhase phase, }) async { final timings = []; final rssValues = []; for (int i = 0; i < warmups; i++) { benchmark.setup(); + if (phase == ResolvePhase.steadyStateResolve) { + benchmark.prewarm(); + } benchmark.run(); benchmark.teardown(); } final memBefore = ProcessInfo.currentRss; for (int i = 0; i < repeats; i++) { benchmark.setup(); + if (phase == ResolvePhase.steadyStateResolve) { + benchmark.prewarm(); + } final sw = Stopwatch()..start(); benchmark.run(); sw.stop(); @@ -78,17 +86,24 @@ class BenchmarkRunner { required UniversalChainAsyncBenchmark benchmark, required int warmups, required int repeats, + required ResolvePhase phase, }) async { final timings = []; final rssValues = []; for (int i = 0; i < warmups; i++) { await benchmark.setup(); + if (phase == ResolvePhase.steadyStateResolve) { + await benchmark.prewarm(); + } await benchmark.run(); await benchmark.teardown(); } final memBefore = ProcessInfo.currentRss; for (int i = 0; i < repeats; i++) { await benchmark.setup(); + if (phase == ResolvePhase.steadyStateResolve) { + await benchmark.prewarm(); + } final sw = Stopwatch()..start(); await benchmark.run(); sw.stop(); diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index 573e55e..7fa975f 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -59,11 +59,16 @@ class UniversalChainModule extends Module { switch (scenario) { case UniversalScenario.register: - // Simple singleton registration. - bind() - .toProvide( - () => UniversalServiceImpl(value: 'reg', dependency: null)) - .singleton(); + if (bindingMode == UniversalBindingMode.lazySingletonStrategy) { + bind() + .toProvide( + () => UniversalServiceImpl(value: 'reg', dependency: null)) + .singleton(); + } else { + bind() + .toInstance( + UniversalServiceImpl(value: 'reg', dependency: null)); + } break; case UniversalScenario.named: // Named factory registration for two distinct objects. @@ -76,6 +81,8 @@ class UniversalChainModule extends Module { break; case UniversalScenario.chain: // Chain of nested services, with dependency on previous level by name. + UniversalService? lastEagerInstance; + final Map eagerInstances = {}; for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { final chain = chainIndex + 1; @@ -84,6 +91,17 @@ class UniversalChainModule extends Module { final depName = '${chain}_$level'; switch (bindingMode) { case UniversalBindingMode.singletonStrategy: + final instance = UniversalServiceImpl( + value: depName, + dependency: eagerInstances[prevDepName], + ); + bind() + .toInstance(instance) + .withName(depName); + eagerInstances[depName] = instance; + lastEagerInstance = instance; + break; + case UniversalBindingMode.lazySingletonStrategy: bind() .toProvide(() => UniversalServiceImpl( value: depName, @@ -116,15 +134,19 @@ class UniversalChainModule extends Module { } } } - // Регистрация алиаса без имени (на последний элемент цепочки) - final depName = '${chainCount}_$nestingDepth'; - bind() - .toProvide( - () => currentScope.resolve(named: depName)) - .singleton(); + // Register unnamed alias for the last chain element. + if (bindingMode == UniversalBindingMode.lazySingletonStrategy) { + final depName = '${chainCount}_$nestingDepth'; + bind() + .toProvide( + () => currentScope.resolve(named: depName)) + .singleton(); + } else if (lastEagerInstance != null) { + bind().toInstance(lastEagerInstance); + } break; case UniversalScenario.override: - // handled at benchmark level, но алиас нужен прямо в этом scope! + // Handled at benchmark level, but alias is needed directly in this scope. final depName = '${chainCount}_$nestingDepth'; bind() .toProvide( @@ -189,7 +211,7 @@ class CherrypickDIAdapter extends DIAdapter { await CherryPick.closeRootScope(); _scope = null; } - // SubScope teardown не требуется + // SubScope teardown is handled by the parent scope. } @override diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index fe00d51..2a2135b 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -4,14 +4,14 @@ import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:get_it/get_it.dart'; import 'di_adapter.dart'; -/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией. +/// DIAdapter for GetIt with scope support and strict typing. class GetItAdapter extends DIAdapter { late GetIt _getIt; final String? _scopeName; final bool _isSubScope; bool _scopePushed = false; - /// Основной (root) и subScope-конструкторы. + /// Root and subScope constructors. GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false}) : _scopeName = scopeName, _isSubScope = isSubScope { @@ -23,7 +23,7 @@ class GetItAdapter extends DIAdapter { @override void setupDependencies(void Function(GetIt container) registration) { if (_isSubScope) { - // Создаём scope через pushNewScope с init + // Create scope via pushNewScope with init _getIt.pushNewScope( scopeName: _scopeName, init: (getIt) => registration(getIt), @@ -41,7 +41,7 @@ class GetItAdapter extends DIAdapter { @override Future resolveAsync({String? named}) async => - _getIt(instanceName: named); + _getIt.getAsync(instanceName: named); @override void teardown() { @@ -92,8 +92,13 @@ class GetItAdapter extends DIAdapter { } break; case UniversalScenario.register: - getIt.registerSingleton( - UniversalServiceImpl(value: 'reg', dependency: null)); + if (bindingMode == UniversalBindingMode.lazySingletonStrategy) { + getIt.registerLazySingleton( + () => UniversalServiceImpl(value: 'reg', dependency: null)); + } else { + getIt.registerSingleton( + UniversalServiceImpl(value: 'reg', dependency: null)); + } break; case UniversalScenario.named: getIt.registerFactory( @@ -120,6 +125,17 @@ class GetItAdapter extends DIAdapter { instanceName: depName, ); break; + case UniversalBindingMode.lazySingletonStrategy: + getIt.registerLazySingleton( + () => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? getIt(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; case UniversalBindingMode.factoryStrategy: getIt.registerFactory( () => UniversalServiceImpl( @@ -154,9 +170,15 @@ class GetItAdapter extends DIAdapter { if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { final depName = '${chainCount}_$nestingDepth'; - getIt.registerSingleton( - getIt(instanceName: depName), - ); + if (bindingMode == UniversalBindingMode.lazySingletonStrategy) { + getIt.registerLazySingleton( + () => getIt(instanceName: depName), + ); + } else { + getIt.registerSingleton( + getIt(instanceName: depName), + ); + } } }; } diff --git a/benchmark_di/lib/di_adapters/kiwi_adapter.dart b/benchmark_di/lib/di_adapters/kiwi_adapter.dart index 1769859..41fd9bb 100644 --- a/benchmark_di/lib/di_adapters/kiwi_adapter.dart +++ b/benchmark_di/lib/di_adapters/kiwi_adapter.dart @@ -4,14 +4,11 @@ import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:kiwi/kiwi.dart'; import 'di_adapter.dart'; -/// DIAdapter-для KiwiContainer с поддержкой universal benchmark сценариев. +/// DIAdapter for KiwiContainer with universal benchmark scenario support. class KiwiAdapter extends DIAdapter { late KiwiContainer _container; - // ignore: unused_field - final bool _isSubScope; - KiwiAdapter({KiwiContainer? container, bool isSubScope = false}) - : _isSubScope = isSubScope { + KiwiAdapter({KiwiContainer? container, bool isSubScope = false}) { _container = container ?? KiwiContainer(); } @@ -57,6 +54,8 @@ class KiwiAdapter extends DIAdapter { final depName = '${chain}_$level'; switch (bindingMode) { case UniversalBindingMode.singletonStrategy: + case UniversalBindingMode.lazySingletonStrategy: + // Kiwi's registerSingleton is lazy (factory-based, cached on first resolve) container.registerSingleton( (c) => UniversalServiceImpl( value: depName, @@ -75,7 +74,7 @@ class KiwiAdapter extends DIAdapter { name: depName); break; case UniversalBindingMode.asyncStrategy: - // Не поддерживается + // Not supported break; } } @@ -97,23 +96,12 @@ class KiwiAdapter extends DIAdapter { @override T resolve({String? named}) { - // Для asyncChain нужен resolve> - if (T.toString().startsWith('Future<')) { - return _container.resolve(named); - } else { - return _container.resolve(named); - } + return _container.resolve(named); } @override - Future resolveAsync({String? named}) async { - if (T.toString().startsWith('Future<')) { - // resolve>, unwrap result - return Future.value(_container.resolve(named)); - } else { - // Для совместимости с chain/override - return Future.value(_container.resolve(named)); - } + Future resolveAsync({String? named}) { + return Future.value(_container.resolve(named)); } @override @@ -123,7 +111,7 @@ class KiwiAdapter extends DIAdapter { @override KiwiAdapter openSubScope(String name) { - // Возвращаем новый scoped контейнер (отдельный). Наследование не реализовано. + // Returns a new scoped container. Inheritance not implemented. return KiwiAdapter(container: KiwiContainer.scoped(), isSubScope: true); } diff --git a/benchmark_di/lib/di_adapters/yx_scope_adapter.dart b/benchmark_di/lib/di_adapters/yx_scope_adapter.dart index a88bbc0..cb34da0 100644 --- a/benchmark_di/lib/di_adapters/yx_scope_adapter.dart +++ b/benchmark_di/lib/di_adapters/yx_scope_adapter.dart @@ -29,8 +29,7 @@ class YxScopeAdapter extends DIAdapter { @override void teardown() { - // У yx_scope нет явного dispose на ScopeContainer, но можно добавить очистку Map/Deps если потребуется - // Ничего не делаем + _scope = UniversalYxScopeContainer(); } @override @@ -54,26 +53,13 @@ class YxScopeAdapter extends DIAdapter { required UniversalBindingMode bindingMode, }) { if (scenario is UniversalScenario) { + if (scenario == UniversalScenario.asyncChain || + bindingMode == UniversalBindingMode.asyncStrategy) { + throw UnsupportedError( + 'YxScope does not support async dependencies or async binding scenarios.'); + } return (scope) { switch (scenario) { - case UniversalScenario.asyncChain: - for (int chain = 1; chain <= chainCount; chain++) { - for (int level = 1; level <= nestingDepth; level++) { - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - final dep = scope.dep( - () => UniversalServiceImpl( - value: depName, - dependency: level > 1 - ? scope.depFor(name: prevDepName).get - : null, - ), - name: depName, - ); - scope.register(dep, name: depName); - } - } - break; case UniversalScenario.register: final dep = scope.dep( () => UniversalServiceImpl(value: 'reg', dependency: null), @@ -113,6 +99,8 @@ class YxScopeAdapter extends DIAdapter { case UniversalScenario.override: // handled at benchmark level break; + default: + break; } if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { diff --git a/benchmark_di/lib/scenarios/universal_binding_mode.dart b/benchmark_di/lib/scenarios/universal_binding_mode.dart index 4907089..d07a599 100644 --- a/benchmark_di/lib/scenarios/universal_binding_mode.dart +++ b/benchmark_di/lib/scenarios/universal_binding_mode.dart @@ -1,9 +1,12 @@ /// Enum to represent the DI registration/binding mode. enum UniversalBindingMode { - /// Singleton/provider binding. + /// Eager singleton — instance created at registration time. singletonStrategy, - /// Factory-based binding. + /// Lazy singleton — instance created on first resolve, then cached. + lazySingletonStrategy, + + /// Factory-based binding — new instance every time. factoryStrategy, /// Async-based binding.