From a5ef0dc4372eee67c3d795b63a45df17af37c063 Mon Sep 17 00:00:00 2001 From: yarashevich_kv Date: Wed, 6 Aug 2025 09:41:17 +0300 Subject: [PATCH 01/32] impr: BENCHMARK - complex benchmark improvements. --- .../lib/complex_bindings_benchmark.dart | 149 +++++++++++++++--- 1 file changed, 130 insertions(+), 19 deletions(-) diff --git a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart index 47b352c..adc2cf4 100644 --- a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart +++ b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart @@ -3,72 +3,179 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; // === DI graph: A -> B -> C (singleton) === -class ServiceA {} -class ServiceB { - final ServiceA a; - ServiceB(this.a); +abstract class Service { + final dynamic value; + final Service? dependency; + + Service({ + required this.value, + this.dependency, + }); } -class ServiceC { - final ServiceB b; - ServiceC(this.b); + +class ServiceImpl extends Service { + ServiceImpl({ + required super.value, + super.dependency, + }); } class ChainSingletonModule extends Module { + // количество независимых цепочек + final int chainCount; + + // глубина вложенности + final int nestingDepth; + + ChainSingletonModule({ + required this.chainCount, + required this.nestingDepth, + }); + @override void builder(Scope currentScope) { - bind().toProvide(() => ServiceA()).singleton(); - bind().toProvide((() => ServiceB(currentScope.resolve()))).singleton(); - bind().toProvide((() => ServiceC(currentScope.resolve()))).singleton(); + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + + final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; + final depName = '${chain.toString()}_${level.toString()}'; + + bind() + .toProvide( + () => ServiceImpl( + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) + .withName(depName) + .singleton(); + } + } } } class ChainSingletonBenchmark extends BenchmarkBase { - ChainSingletonBenchmark() : super('ChainSingleton (A->B->C, singleton)'); + final int chainCount; + final int nestingDepth; + + ChainSingletonBenchmark({ + this.chainCount = 1, + this.nestingDepth = 3, + }) : super( + 'ChainSingleton (A->B->C, singleton). ' + 'C/D = $chainCount/$nestingDepth. ', + ); late Scope scope; + @override void setup() { scope = CherryPick.openRootScope(); - scope.installModules([ChainSingletonModule()]); + scope.installModules([ + ChainSingletonModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + ), + ]); } + @override void teardown() => CherryPick.closeRootScope(); + @override void run() { - scope.resolve(); + final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; + scope.resolve(named: serviceName); } } // === DI graph: A -> B -> C (factory/no singleton) === class ChainFactoryModule extends Module { + // количество независимых цепочек + final int chainCount; + + // глубина вложенности + final int nestingDepth; + + ChainFactoryModule({ + required this.chainCount, + required this.nestingDepth, + }); + @override void builder(Scope currentScope) { - bind().toProvide(() => ServiceA()); - bind().toProvide((() => ServiceB(currentScope.resolve()))); - bind().toProvide((() => ServiceC(currentScope.resolve()))); + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + + final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; + final depName = '${chain.toString()}_${level.toString()}'; + + bind() + .toProvide( + () => ServiceImpl( + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) + .withName(depName); + } + } } } class ChainFactoryBenchmark extends BenchmarkBase { - ChainFactoryBenchmark() : super('ChainFactory (A->B->C, factory)'); + // количество независимых цепочек + final int chainCount; + + // глубина вложенности + final int nestingDepth; + + ChainFactoryBenchmark({ + this.chainCount = 1, + this.nestingDepth = 3, + }) : super( + 'ChainFactory (A->B->C, factory). ' + 'C/D = $chainCount/$nestingDepth. ', + ); + late Scope scope; + @override void setup() { CherryPick.disableGlobalCycleDetection(); CherryPick.disableGlobalCrossScopeCycleDetection(); + scope = CherryPick.openRootScope(); - scope.installModules([ChainFactoryModule()]); + scope.installModules([ + ChainFactoryModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + ), + ]); } + @override void teardown() => CherryPick.closeRootScope(); + @override void run() { - scope.resolve(); + final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; + scope.resolve(named: serviceName); } } // === Named bindings: Multiple implementations === class Impl1 {} + class Impl2 {} + class NamedModule extends Module { @override void builder(Scope currentScope) { @@ -80,16 +187,20 @@ class NamedModule extends Module { class NamedResolveBenchmark extends BenchmarkBase { NamedResolveBenchmark() : super('NamedResolve (by name)'); late Scope scope; + @override void setup() { scope = CherryPick.openRootScope(); scope.installModules([NamedModule()]); } + @override void teardown() => CherryPick.closeRootScope(); + @override void run() { // Switch name for comparison scope.resolve(named: 'impl2'); } } + From 926bbf15f4aa1622395d2d6ac6cb5efa4e3897e8 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 13:29:23 +0300 Subject: [PATCH 02/32] refactor(benchmarks): unify benchmark structure, enable CLI parameterization, run matrix, add CSV/JSON/pretty output - All benchmarks now use a unified base mixin for setup/teardown (BenchmarkWithScope). - Added args package support: CLI flags for choosing benchmarks, chain counts, nesting depths, output format. - Support for running benchmarks in matrix mode (multiple parameter sets). - Machine-readable output: csv, json, pretty-table. - Loop and naming lint fixes, unused imports removed. --- benchmark_cherrypick/bin/main.dart | 126 ++++++++++++++++-- .../lib/async_chain_benchmark.dart | 25 ++-- benchmark_cherrypick/lib/benchmark_utils.dart | 29 ++++ .../lib/cherrypick_benchmark.dart | 4 +- .../lib/complex_bindings_benchmark.dart | 69 ++++------ .../lib/scope_override_benchmark.dart | 18 +-- benchmark_cherrypick/pubspec.lock | 10 +- benchmark_cherrypick/pubspec.yaml | 1 + 8 files changed, 211 insertions(+), 71 deletions(-) create mode 100644 benchmark_cherrypick/lib/benchmark_utils.dart diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 7614e7c..026b0c1 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -2,16 +2,124 @@ import 'package:benchmark_cherrypick/cherrypick_benchmark.dart'; import 'package:benchmark_cherrypick/complex_bindings_benchmark.dart'; import 'package:benchmark_cherrypick/async_chain_benchmark.dart'; import 'package:benchmark_cherrypick/scope_override_benchmark.dart'; +import 'package:args/args.dart'; -void main(List args) async { - // Синхронные бенчмарки - RegisterAndResolveBenchmark().report(); - ChainSingletonBenchmark().report(); - ChainFactoryBenchmark().report(); - NamedResolveBenchmark().report(); +Future main(List args) async { + final parser = ArgParser() + ..addOption('benchmark', abbr: 'b', help: 'Benchmark name (register, chain_singleton, chain_factory, named, override, async_chain, all)', defaultsTo: 'all') + ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts (используется в chain_singleton/factory)', defaultsTo: '100') + ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths (используется в chain_singleton/factory)', defaultsTo: '100') + ..addOption('format', abbr: 'f', help: 'Output format (pretty, csv, json)', defaultsTo: 'pretty') + ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); - // Асинхронный бенчмарк - await AsyncChainBenchmark().report(); + final result = parser.parse(args); - ScopeOverrideBenchmark().report(); + if (result['help'] == true) { + print('Dart DI benchmarks'); + print(parser.usage); + return; + } + + final benchmark = result['benchmark'] as String; + final format = result['format'] as String; + + final chainCounts = _parseIntList(result['chainCount'] as String); + final nestDepths = _parseIntList(result['nestingDepth'] as String); + + final results = >[]; + void addResult(String name, int? chainCount, int? nestingDepth, num elapsed) { + results.add({ + 'benchmark': name, + 'chainCount': chainCount, + 'nestingDepth': nestingDepth, + 'elapsed_us': elapsed.round() + }); + } + + Future runAndCollect(String name, Future Function() fn, {int? chainCount, int? nestingDepth}) async { + final elapsed = await fn(); + addResult(name, chainCount, nestingDepth, elapsed); + } + + if (benchmark == 'all' || benchmark == 'register') { + await runAndCollect('RegisterAndResolve', () async { + return _captureReport(RegisterAndResolveBenchmark().report); + }); + } + if (benchmark == 'all' || benchmark == 'chain_singleton') { + for (final c in chainCounts) { + for (final d in nestDepths) { + await runAndCollect('ChainSingleton', () async { + return _captureReport(() => ChainSingletonBenchmark(chainCount: c, nestingDepth: d).report()); + }, chainCount: c, nestingDepth: d); + } + } + } + if (benchmark == 'all' || benchmark == 'chain_factory') { + for (final c in chainCounts) { + for (final d in nestDepths) { + await runAndCollect('ChainFactory', () async { + return _captureReport(() => ChainFactoryBenchmark(chainCount: c, nestingDepth: d).report()); + }, chainCount: c, nestingDepth: d); + } + } + } + if (benchmark == 'all' || benchmark == 'named') { + await runAndCollect('NamedResolve', () async { + return _captureReport(NamedResolveBenchmark().report); + }); + } + if (benchmark == 'all' || benchmark == 'override') { + await runAndCollect('ScopeOverride', () async { + return _captureReport(ScopeOverrideBenchmark().report); + }); + } + if (benchmark == 'all' || benchmark == 'async_chain') { + await runAndCollect('AsyncChain', () async { + return _captureReportAsync(AsyncChainBenchmark().report); + }); + } + + if (format == 'json') { + print(_toJson(results)); + } else if (format == 'csv') { + print(_toCsv(results)); + } else { + print(_toPretty(results)); + } } + +// --- helpers --- +List _parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); + +Future _captureReport(void Function() fn) async { + final sw = Stopwatch()..start(); + fn(); + sw.stop(); + return sw.elapsedMicroseconds; +} +Future _captureReportAsync(Future Function() fn) async { + final sw = Stopwatch()..start(); + await fn(); + sw.stop(); + return sw.elapsedMicroseconds; +} + +String _toPretty(List> rows) { + final keys = ['benchmark','chainCount','nestingDepth','elapsed_us']; + final header = keys.join('\t'); + final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); + return ([header] + lines).join('\n'); +} + +String _toCsv(List> rows) { + final keys = ['benchmark','chainCount','nestingDepth','elapsed_us']; + final header = keys.join(','); + final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join(',')).toList(); + return ([header] + lines).join('\n'); +} + +String _toJson(List> rows) { + return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; +} +// --- end helpers --- diff --git a/benchmark_cherrypick/lib/async_chain_benchmark.dart b/benchmark_cherrypick/lib/async_chain_benchmark.dart index 59f74a6..7b27184 100644 --- a/benchmark_cherrypick/lib/async_chain_benchmark.dart +++ b/benchmark_cherrypick/lib/async_chain_benchmark.dart @@ -1,12 +1,15 @@ // ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; +import 'benchmark_utils.dart'; class AsyncA {} + class AsyncB { final AsyncA a; AsyncB(this.a); } + class AsyncC { final AsyncB b; AsyncC(this.b); @@ -16,26 +19,30 @@ class AsyncChainModule extends Module { @override void builder(Scope currentScope) { bind().toProvideAsync(() async => AsyncA()).singleton(); - bind().toProvideAsync(() async => AsyncB(await currentScope.resolveAsync())).singleton(); - bind().toProvideAsync(() async => AsyncC(await currentScope.resolveAsync())).singleton(); + bind() + .toProvideAsync( + () async => AsyncB(await currentScope.resolveAsync())) + .singleton(); + bind() + .toProvideAsync( + () async => AsyncC(await currentScope.resolveAsync())) + .singleton(); } } -class AsyncChainBenchmark extends AsyncBenchmarkBase { +class AsyncChainBenchmark extends AsyncBenchmarkBase with BenchmarkWithScope { AsyncChainBenchmark() : super('AsyncChain (A->B->C, async)'); - late Scope scope; @override Future setup() async { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - scope = CherryPick.openRootScope(); - scope.installModules([AsyncChainModule()]); + setupScope([AsyncChainModule()]); } + @override Future teardown() async { - CherryPick.closeRootScope(); + teardownScope(); } + @override Future run() async { await scope.resolveAsync(); diff --git a/benchmark_cherrypick/lib/benchmark_utils.dart b/benchmark_cherrypick/lib/benchmark_utils.dart new file mode 100644 index 0000000..4d1a302 --- /dev/null +++ b/benchmark_cherrypick/lib/benchmark_utils.dart @@ -0,0 +1,29 @@ +import 'package:cherrypick/cherrypick.dart'; + +/// Миксин для упрощения работы с CherryPick Scope в бенчмарках. +mixin BenchmarkWithScope { + Scope? _scope; + + /// Отключить глобальные проверки циклов и создать корневой scope с модулями. + void setupScope(List modules, + {bool disableCycleDetection = true, + bool disableCrossScopeCycleDetection = true}) { + if (disableCycleDetection) { + CherryPick.disableGlobalCycleDetection(); + } + if (disableCrossScopeCycleDetection) { + CherryPick.disableGlobalCrossScopeCycleDetection(); + } + _scope = CherryPick.openRootScope(); + _scope!.installModules(modules); + } + + /// Закрывает текущий scope. + void teardownScope() { + CherryPick.closeRootScope(); + _scope = null; + } + + /// Получить текущий scope. Не null после setupScope. + Scope get scope => _scope!; +} diff --git a/benchmark_cherrypick/lib/cherrypick_benchmark.dart b/benchmark_cherrypick/lib/cherrypick_benchmark.dart index a6355bf..87c53f0 100644 --- a/benchmark_cherrypick/lib/cherrypick_benchmark.dart +++ b/benchmark_cherrypick/lib/cherrypick_benchmark.dart @@ -5,9 +5,8 @@ import 'package:cherrypick/cherrypick.dart'; class AppModule extends Module { @override void builder(Scope currentScope) { - bind().toProvide(() => FooService()); + bind().toProvide(() => FooService()); } - } // Dummy service for DI @@ -23,7 +22,6 @@ class RegisterAndResolveBenchmark extends BenchmarkBase { CherryPick.disableGlobalCrossScopeCycleDetection(); scope = CherryPick.openRootScope(); scope.installModules([AppModule()]); - } @override diff --git a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart index adc2cf4..b2754e8 100644 --- a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart +++ b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart @@ -1,6 +1,7 @@ // ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; +import 'benchmark_utils.dart'; // === DI graph: A -> B -> C (singleton) === abstract class Service { @@ -45,12 +46,12 @@ class ChainSingletonModule extends Module { bind() .toProvide( () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) .withName(depName) .singleton(); } @@ -58,7 +59,7 @@ class ChainSingletonModule extends Module { } } -class ChainSingletonBenchmark extends BenchmarkBase { +class ChainSingletonBenchmark extends BenchmarkBase with BenchmarkWithScope { final int chainCount; final int nestingDepth; @@ -66,15 +67,13 @@ class ChainSingletonBenchmark extends BenchmarkBase { this.chainCount = 1, this.nestingDepth = 3, }) : super( - 'ChainSingleton (A->B->C, singleton). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - late Scope scope; + 'ChainSingleton (A->B->C, singleton). ' + 'C/D = $chainCount/$nestingDepth. ', + ); @override void setup() { - scope = CherryPick.openRootScope(); - scope.installModules([ + setupScope([ ChainSingletonModule( chainCount: chainCount, nestingDepth: nestingDepth, @@ -83,7 +82,7 @@ class ChainSingletonBenchmark extends BenchmarkBase { } @override - void teardown() => CherryPick.closeRootScope(); + void teardown() => teardownScope(); @override void run() { @@ -118,42 +117,33 @@ class ChainFactoryModule extends Module { bind() .toProvide( () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) .withName(depName); } } } } -class ChainFactoryBenchmark extends BenchmarkBase { - // количество независимых цепочек +class ChainFactoryBenchmark extends BenchmarkBase with BenchmarkWithScope { final int chainCount; - - // глубина вложенности final int nestingDepth; ChainFactoryBenchmark({ this.chainCount = 1, this.nestingDepth = 3, }) : super( - 'ChainFactory (A->B->C, factory). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - - late Scope scope; + 'ChainFactory (A->B->C, factory). ' + 'C/D = $chainCount/$nestingDepth. ', + ); @override void setup() { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - - scope = CherryPick.openRootScope(); - scope.installModules([ + setupScope([ ChainFactoryModule( chainCount: chainCount, nestingDepth: nestingDepth, @@ -162,7 +152,7 @@ class ChainFactoryBenchmark extends BenchmarkBase { } @override - void teardown() => CherryPick.closeRootScope(); + void teardown() => teardownScope(); @override void run() { @@ -184,18 +174,16 @@ class NamedModule extends Module { } } -class NamedResolveBenchmark extends BenchmarkBase { +class NamedResolveBenchmark extends BenchmarkBase with BenchmarkWithScope { NamedResolveBenchmark() : super('NamedResolve (by name)'); - late Scope scope; @override void setup() { - scope = CherryPick.openRootScope(); - scope.installModules([NamedModule()]); + setupScope([NamedModule()]); } @override - void teardown() => CherryPick.closeRootScope(); + void teardown() => teardownScope(); @override void run() { @@ -203,4 +191,3 @@ class NamedResolveBenchmark extends BenchmarkBase { scope.resolve(named: 'impl2'); } } - diff --git a/benchmark_cherrypick/lib/scope_override_benchmark.dart b/benchmark_cherrypick/lib/scope_override_benchmark.dart index b3231dd..868ee69 100644 --- a/benchmark_cherrypick/lib/scope_override_benchmark.dart +++ b/benchmark_cherrypick/lib/scope_override_benchmark.dart @@ -1,9 +1,12 @@ // ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; +import 'benchmark_utils.dart'; class Shared {} + class ParentImpl extends Shared {} + class ChildImpl extends Shared {} class ParentModule extends Module { @@ -20,23 +23,22 @@ class ChildOverrideModule extends Module { } } -class ScopeOverrideBenchmark extends BenchmarkBase { +class ScopeOverrideBenchmark extends BenchmarkBase with BenchmarkWithScope { ScopeOverrideBenchmark() : super('ScopeOverride (child overrides parent)'); - late Scope parent; late Scope child; + @override void setup() { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - parent = CherryPick.openRootScope(); - parent.installModules([ParentModule()]); - child = parent.openSubScope('child'); + setupScope([ParentModule()]); + child = scope.openSubScope('child'); child.installModules([ChildOverrideModule()]); } + @override void teardown() { - CherryPick.closeRootScope(); + teardownScope(); } + @override void run() { // Должен возвращать ChildImpl, а не ParentImpl diff --git a/benchmark_cherrypick/pubspec.lock b/benchmark_cherrypick/pubspec.lock index 31ef9ca..2b5229b 100644 --- a/benchmark_cherrypick/pubspec.lock +++ b/benchmark_cherrypick/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4" + args: + dependency: "direct main" + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" benchmark_harness: dependency: "direct dev" description: @@ -31,7 +39,7 @@ packages: path: "../cherrypick" relative: true source: path - version: "3.0.0-dev.1" + version: "3.0.0-dev.2" exception_templates: dependency: transitive description: diff --git a/benchmark_cherrypick/pubspec.yaml b/benchmark_cherrypick/pubspec.yaml index d885d4d..c3a3074 100644 --- a/benchmark_cherrypick/pubspec.yaml +++ b/benchmark_cherrypick/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: cherrypick: path: ../cherrypick + args: ^2.7.0 dev_dependencies: lints: ^5.0.0 From 7f488f873ee17a2ed5c789dd8503d0d8811b33ff Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 13:35:39 +0300 Subject: [PATCH 03/32] docs(benchmarks): update README files with new CLI, matrix run, output formats, and usage instructions (EN+RU) --- benchmark_cherrypick/README.md | 97 +++++++++++++++++++++++------- benchmark_cherrypick/README.ru.md | 99 ++++++++++++++++++++++++------- 2 files changed, 155 insertions(+), 41 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index 81fe8a2..74ace69 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -1,28 +1,29 @@ # benchmark_cherrypick -Benchmarks for performance and features of the cherrypick (core) DI container. +Benchmarks for the performance and features of the cherrypick (core) DI container. -All scenarios use the public API capabilities of cherrypick (scope, module, binding, scoping, and async support). +All scenarios use only the public API (scope, module, binding, scoping, and async). ## Scenarios -- **RegisterAndResolve**: basic registration and resolution of a dependency. -- **ChainSingleton (A->B->C, singleton)**: dependency chain, all as singletons. -- **ChainFactory (A->B->C, factory)**: dependency chain with factory bindings (new instance on each request). -- **NamedResolve (by name)**: resolving a named dependency among multiple implementations. -- **AsyncChain (A->B->C, async)**: asynchronous dependency chain. -- **ScopeOverride (child overrides parent)**: overriding a dependency in a child scope over a parent. +- **RegisterAndResolve**: Basic registration and resolution of a dependency. +- **ChainSingleton (A->B->C, singleton)**: Deep dependency chain, all as singletons. +- **ChainFactory (A->B->C, factory)**: Dependency chain with factory bindings (new instance per request). +- **NamedResolve (by name)**: Resolving a named dependency among several implementations. +- **AsyncChain (A->B->C, async)**: Asynchronous dependency chain. +- **ScopeOverride (child overrides parent)**: Overriding a dependency in a child scope over a parent. -## Benchmark results +## Features -| Scenario | RunTime (μs) | -|----------------------------------------------------|---------------| -| RegisterAndResolve | 0.4574 | -| ChainSingleton (A->B->C, singleton) | 0.3759 | -| ChainFactory (A->B->C, factory) | 1.3783 | -| NamedResolve (by name) | 0.5193 | -| AsyncChain (A->B->C, async) | 0.5985 | -| ScopeOverride (child overrides parent) | 0.3611 | +- **Unified benchmark structure**: All test scenarios use a unified base mixin for setup/teardown. +- **Flexible CLI parameterization**: + Run benchmarks with custom parameters for chain length, depth, scenarios and formats. +- **Matrix/mass run support**: + Easily run benchmarks for all combinations of chainLength and depth in one run. +- **Machine and human readable output**: + Supports pretty-table, CSV, and JSON for downstream analytics or data storage. +- **Scenario selection**: + Run all or only specific benchmarks via CLI. ## How to run @@ -30,13 +31,69 @@ All scenarios use the public API capabilities of cherrypick (scope, module, bind ```shell dart pub get ``` -2. Run the benchmarks: +2. Run all benchmarks (with default parameters): ```shell dart run bin/main.dart ``` -A text report with all metrics will be displayed in the console. +### Run with custom parameters + +- Mass run in matrix mode (CSV output): + ```shell + dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv + ``` + +- Run only the named resolve scenario: + ```shell + dart run bin/main.dart --benchmark=named + ``` + +- See available CLI flags: + ```shell + dart run bin/main.dart --help + ``` + +#### Available CLI options + +- `--benchmark` (or `-b`) — Scenario to run: + `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (default) +- `--chainCount` (or `-c`) — Comma-separated chain lengths. E.g. `10,100` +- `--nestingDepth` (or `-d`) — Comma-separated chain depths. E.g. `5,10` +- `--format` (or `-f`) — Result output format: `pretty` (table), `csv`, `json` +- `--help` (or `-h`) — Print help + +#### Example output (`--format=csv`) +``` +benchmark,chainCount,nestingDepth,elapsed_us +ChainSingleton,10,5,2450000 +ChainSingleton,10,10,2624000 +ChainSingleton,100,5,2506300 +ChainSingleton,100,10,2856900 +``` --- -To add your custom scenario — just create a new Dart file and declare a class extending BenchmarkBase or AsyncBenchmarkBase, then add its invocation to main.dart. +## Add your own benchmark + +1. Create a Dart file with a class inheriting from `BenchmarkBase` or `AsyncBenchmarkBase`. +2. Use the `BenchmarkWithScope` mixin for automatic Scope management if needed. +3. Add your benchmark to bin/main.dart for selection via CLI. + +--- + +## Example for contributors + +```dart +class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { + MyBenchmark() : super('My custom'); + @override void setup() => setupScope([MyModule()]); + @override void run() { scope.resolve(); } + @override void teardown() => teardownScope(); +} +``` + +--- + +## License + +MIT diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index 86a9539..37a5690 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -1,42 +1,99 @@ # benchmark_cherrypick -Бенчмарки производительности и функциональности DI-контейнера cherrypick (core). +Бенчмарки производительности и возможностей DI-контейнера cherrypick (core). -Все сценарии используют реальные возможности public API cherrypick (scope, module, binding, scoping и асинхронность). +Все сценарии используют только публичное API (scope, module, binding, scoping, async). ## Сценарии -- **RegisterAndResolve**: базовая операция регистрации и разрешения зависимости. -- **ChainSingleton (A->B->C, singleton)**: цепочка зависимостей, все singletons. -- **ChainFactory (A->B->C, factory)**: цепочка зависимостей с factory биндингами, новые объекты на каждый запрос. -- **NamedResolve (by name)**: разрешение именованной зависимости среди нескольких реализаций. +- **RegisterAndResolve**: базовая регистрация и разрешение зависимости. +- **ChainSingleton (A->B->C, singleton)**: длинная цепочка зависимостей, все как singleton. +- **ChainFactory (A->B->C, factory)**: цепочка зависимостей через factory (новый объект на каждый запрос). +- **NamedResolve (by name)**: разрешение зависимости по имени среди нескольких реализаций. - **AsyncChain (A->B->C, async)**: асинхронная цепочка зависимостей. -- **ScopeOverride (child overrides parent)**: переопределение зависимости в дочернем scope над родительским. +- **ScopeOverride (child overrides parent)**: перекрытие зависимости в дочернем scope относительно родителя. -## Результаты исследования +## Возможности -| Сценарий | RunTime (мкс) | -|----------------------------------------------------|--------------| -| RegisterAndResolve | 0.4574 | -| ChainSingleton (A->B->C, singleton) | 0.3759 | -| ChainFactory (A->B->C, factory) | 1.3783 | -| NamedResolve (by name) | 0.5193 | -| AsyncChain (A->B->C, async) | 0.5985 | -| ScopeOverride (child overrides parent) | 0.3611 | +- **Унифицированная структура бенчмарков**: Все тесты используют единый миксин для setup/teardown (`BenchmarkWithScope`). +- **Гибкая параметризация CLI**: + Любые комбинации параметров chainCount, nestingDepth, сценария и формата вывода. +- **Массовый/матричный запуск**: + Перебор всех вариантов комбинаций chainCount и depth одной командой. +- **Машино- и человекочитаемый вывод**: + Поддержка pretty-таблицы, CSV, JSON — удобно для анализа результатов. +- **Выбор сценария**: + Запуск всех сценариев или только нужного через CLI. -## Как запускать +## Как запустить -1. Получить зависимости: +1. Установить зависимости: ```shell dart pub get ``` -2. Запустить бенчмарк: +2. Запустить все бенчмарки с параметрами по умолчанию: ```shell dart run bin/main.dart ``` -Будет показан текстовый отчёт по всем метрикам. +### Запуск с параметрами + +- Матричный прогон (csv-вывод): + ```shell + dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv + ``` + +- Только сценарий разрешения по имени: + ```shell + dart run bin/main.dart --benchmark=named + ``` + +- Справка по командам: + ```shell + dart run bin/main.dart --help + ``` + +#### CLI-флаги + +- `--benchmark` (или `-b`) — Сценарий: + `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (по умолчанию) +- `--chainCount` (или `-c`) — Через запятую, несколько длин цепочек. Например: `10,100` +- `--nestingDepth` (или `-d`) — Через запятую, глубины цепочек. Например: `5,10` +- `--format` (или `-f`) — Формат вывода: `pretty` (таблица), `csv`, `json` +- `--help` (или `-h`) — Показать справку + +#### Пример вывода (`--format=csv`) +``` +benchmark,chainCount,nestingDepth,elapsed_us +ChainSingleton,10,5,2450000 +ChainSingleton,10,10,2624000 +ChainSingleton,100,5,2506300 +ChainSingleton,100,10,2856900 +``` --- -Если хотите добавить свой сценарий — создайте отдельный Dart-файл и объявите новый BenchmarkBase/AsyncBenchmarkBase, не забудьте вставить его вызов в main. +## Добавить свой бенчмарк + +1. Создайте Dart-файл с классом, наследующим BenchmarkBase или AsyncBenchmarkBase. +2. Используйте миксин BenchmarkWithScope для автоматического управления Scope. +3. Добавьте его вызов в bin/main.dart для выбора через CLI. + +--- + +## Пример для контрибуторов + +```dart +class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { + MyBenchmark() : super('My custom'); + @override void setup() => setupScope([MyModule()]); + @override void run() { scope.resolve(); } + @override void teardown() => teardownScope(); +} +``` + +--- + +## Лицензия + +MIT From 6928daa50eab28337076173ce0c35c594836e8fd Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 13:48:51 +0300 Subject: [PATCH 04/32] docs(benchmarks): update README files with stability options, repeat/warmup params, stat fields, and usage examples --- benchmark_cherrypick/README.md | 62 ++++++++++++-------------- benchmark_cherrypick/README.ru.md | 72 ++++++++++++++----------------- 2 files changed, 61 insertions(+), 73 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index 74ace69..140e659 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -2,28 +2,23 @@ Benchmarks for the performance and features of the cherrypick (core) DI container. -All scenarios use only the public API (scope, module, binding, scoping, and async). - ## Scenarios - **RegisterAndResolve**: Basic registration and resolution of a dependency. -- **ChainSingleton (A->B->C, singleton)**: Deep dependency chain, all as singletons. -- **ChainFactory (A->B->C, factory)**: Dependency chain with factory bindings (new instance per request). -- **NamedResolve (by name)**: Resolving a named dependency among several implementations. -- **AsyncChain (A->B->C, async)**: Asynchronous dependency chain. -- **ScopeOverride (child overrides parent)**: Overriding a dependency in a child scope over a parent. +- **ChainSingleton** (A->B->C, singleton): Deep dependency chain, all as singletons. +- **ChainFactory** (A->B->C, factory): Dependency chain with factory bindings (new instance per request). +- **NamedResolve** (by name): Resolving a named dependency among several implementations. +- **AsyncChain** (A->B->C, async): Asynchronous dependency chain. +- **ScopeOverride** (child overrides parent): Overriding a dependency in a child scope over a parent. ## Features -- **Unified benchmark structure**: All test scenarios use a unified base mixin for setup/teardown. -- **Flexible CLI parameterization**: - Run benchmarks with custom parameters for chain length, depth, scenarios and formats. -- **Matrix/mass run support**: - Easily run benchmarks for all combinations of chainLength and depth in one run. -- **Machine and human readable output**: - Supports pretty-table, CSV, and JSON for downstream analytics or data storage. -- **Scenario selection**: - Run all or only specific benchmarks via CLI. +- **Unified benchmark structure** +- **Flexible CLI parameterization (chain length, depth, repeats, warmup, scenario selection, format)** +- **Automatic matrix/mass run for sets of parameters** +- **Statistics: mean, median, stddev, min, max for each scenario** +- **Pretty-table, CSV, and JSON output** +- **Warmup runs before timing for better result stability** ## How to run @@ -31,21 +26,21 @@ All scenarios use only the public API (scope, module, binding, scoping, and asyn ```shell dart pub get ``` -2. Run all benchmarks (with default parameters): +2. Run all benchmarks (defaults: single parameter set, repeat=5, warmup=2): ```shell dart run bin/main.dart ``` -### Run with custom parameters +### Custom parameters -- Mass run in matrix mode (CSV output): +- Matrix run (CSV, 7 repeats, 3 warmups): ```shell - dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv + dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --repeat=7 --warmup=3 --format=csv ``` - Run only the named resolve scenario: ```shell - dart run bin/main.dart --benchmark=named + dart run bin/main.dart --benchmark=named --repeat=3 --warmup=1 ``` - See available CLI flags: @@ -53,22 +48,21 @@ All scenarios use only the public API (scope, module, binding, scoping, and asyn dart run bin/main.dart --help ``` -#### Available CLI options +#### CLI options -- `--benchmark` (or `-b`) — Scenario to run: - `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (default) -- `--chainCount` (or `-c`) — Comma-separated chain lengths. E.g. `10,100` -- `--nestingDepth` (or `-d`) — Comma-separated chain depths. E.g. `5,10` -- `--format` (or `-f`) — Result output format: `pretty` (table), `csv`, `json` -- `--help` (or `-h`) — Print help +- `--benchmark` (`-b`) — Scenario: + `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (default: all) +- `--chainCount` (`-c`) — Comma-separated chain lengths. E.g. `10,100` +- `--nestingDepth` (`-d`) — Comma-separated chain depths. E.g. `5,10` +- `--repeat` (`-r`) — How many times to measure each scenario (`default: 5`) +- `--warmup` (`-w`) — How many warmup runs before actual timing (`default: 2`) +- `--format` (`-f`) — Output: `pretty`, `csv`, `json` (default: pretty) +- `--help` (`-h`) — Print help #### Example output (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,elapsed_us -ChainSingleton,10,5,2450000 -ChainSingleton,10,10,2624000 -ChainSingleton,100,5,2506300 -ChainSingleton,100,10,2856900 +benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us +ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000" ``` --- @@ -81,7 +75,7 @@ ChainSingleton,100,10,2856900 --- -## Example for contributors +## Contributor example ```dart class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index 37a5690..4b04c7c 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -2,28 +2,23 @@ Бенчмарки производительности и возможностей DI-контейнера cherrypick (core). -Все сценарии используют только публичное API (scope, module, binding, scoping, async). - ## Сценарии - **RegisterAndResolve**: базовая регистрация и разрешение зависимости. -- **ChainSingleton (A->B->C, singleton)**: длинная цепочка зависимостей, все как singleton. -- **ChainFactory (A->B->C, factory)**: цепочка зависимостей через factory (новый объект на каждый запрос). -- **NamedResolve (by name)**: разрешение зависимости по имени среди нескольких реализаций. -- **AsyncChain (A->B->C, async)**: асинхронная цепочка зависимостей. -- **ScopeOverride (child overrides parent)**: перекрытие зависимости в дочернем scope относительно родителя. +- **ChainSingleton** (A->B->C, singleton): длинная цепочка зависимостей, все как singleton. +- **ChainFactory** (A->B->C, factory): цепочка зависимостей через factory (новый объект на каждый запрос). +- **NamedResolve** (by name): разрешение зависимости по имени среди нескольких реализаций. +- **AsyncChain** (A->B->C, async): асинхронная цепочка зависимостей. +- **ScopeOverride** (child overrides parent): перекрытие зависимости в дочернем scope относительно родителя. ## Возможности -- **Унифицированная структура бенчмарков**: Все тесты используют единый миксин для setup/teardown (`BenchmarkWithScope`). -- **Гибкая параметризация CLI**: - Любые комбинации параметров chainCount, nestingDepth, сценария и формата вывода. -- **Массовый/матричный запуск**: - Перебор всех вариантов комбинаций chainCount и depth одной командой. -- **Машино- и человекочитаемый вывод**: - Поддержка pretty-таблицы, CSV, JSON — удобно для анализа результатов. -- **Выбор сценария**: - Запуск всех сценариев или только нужного через CLI. +- **Унифицированная структура бенчмарков** +- **Гибкая параметризация CLI (chainCount, nestingDepth, repeats, warmup, сценарий, формат)** +- **Автоматический матричный запуск для наборов параметров** +- **Статистика: среднее, медиана, stddev, min, max для каждого сценария** +- **Вывод в таблицу, CSV или JSON** +- **Прогревочные запуски до замера времени для стабильности** ## Как запустить @@ -31,53 +26,52 @@ ```shell dart pub get ``` -2. Запустить все бенчмарки с параметрами по умолчанию: +2. Запустить все бенчмарки (по умолчанию: одни значения, repeat=5, warmup=2): ```shell dart run bin/main.dart ``` -### Запуск с параметрами +### Пользовательские параметры -- Матричный прогон (csv-вывод): +- Матричный прогон (csv, 7 повторов, 3 прогрева): ```shell - dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv + dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --repeat=7 --warmup=3 --format=csv ``` -- Только сценарий разрешения по имени: +- Только сценарий с именованным разрешением: ```shell - dart run bin/main.dart --benchmark=named + dart run bin/main.dart --benchmark=named --repeat=3 --warmup=1 ``` -- Справка по командам: +- Посмотреть все флаги CLI: ```shell dart run bin/main.dart --help ``` -#### CLI-флаги +#### Опции CLI -- `--benchmark` (или `-b`) — Сценарий: - `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (по умолчанию) -- `--chainCount` (или `-c`) — Через запятую, несколько длин цепочек. Например: `10,100` -- `--nestingDepth` (или `-d`) — Через запятую, глубины цепочек. Например: `5,10` -- `--format` (или `-f`) — Формат вывода: `pretty` (таблица), `csv`, `json` -- `--help` (или `-h`) — Показать справку +- `--benchmark` (`-b`) — Сценарий: + `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (по умолчанию all) +- `--chainCount` (`-c`) — Длины цепочек через запятую. Напр: `10,100` +- `--nestingDepth` (`-d`) — Глубины цепочек через запятую. Напр: `5,10` +- `--repeat` (`-r`) — Сколько раз мерить каждую конфигурацию (`по умолчанию: 5`) +- `--warmup` (`-w`) — Сколько прогревочных запусков до замера времени (`по умолчанию: 2`) +- `--format` (`-f`) — Вывод: `pretty`, `csv`, `json` (по умолчанию pretty) +- `--help` (`-h`) — Показать справку #### Пример вывода (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,elapsed_us -ChainSingleton,10,5,2450000 -ChainSingleton,10,10,2624000 -ChainSingleton,100,5,2506300 -ChainSingleton,100,10,2856900 +benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us +ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000" ``` --- -## Добавить свой бенчмарк +## Как добавить свой бенчмарк -1. Создайте Dart-файл с классом, наследующим BenchmarkBase или AsyncBenchmarkBase. -2. Используйте миксин BenchmarkWithScope для автоматического управления Scope. -3. Добавьте его вызов в bin/main.dart для выбора через CLI. +1. Создайте Dart-файл с классом, унаследованным от `BenchmarkBase` или `AsyncBenchmarkBase`. +2. Используйте миксин `BenchmarkWithScope` для управления Scope (если нужно). +3. Добавьте ваш бенчмарк в bin/main.dart для запуска через CLI. --- From 18905a068db0e639fe808be43e2a63206c199012 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 14:07:44 +0300 Subject: [PATCH 05/32] docs(benchmarks): document memory_diff_kb, delta_peak_kb, peak_rss_kb in README files (EN+RU) --- benchmark_cherrypick/README.md | 5 +++-- benchmark_cherrypick/README.ru.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index 140e659..629e0ce 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -17,6 +17,7 @@ Benchmarks for the performance and features of the cherrypick (core) DI containe - **Flexible CLI parameterization (chain length, depth, repeats, warmup, scenario selection, format)** - **Automatic matrix/mass run for sets of parameters** - **Statistics: mean, median, stddev, min, max for each scenario** +- **Memory metrics: memory_diff_kb (total diff), delta_peak_kb (max growth), peak_rss_kb (absolute peak)** - **Pretty-table, CSV, and JSON output** - **Warmup runs before timing for better result stability** @@ -61,8 +62,8 @@ Benchmarks for the performance and features of the cherrypick (core) DI containe #### Example output (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us -ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000" +benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us,memory_diff_kb,delta_peak_kb,peak_rss_kb +ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000",-64,0,200064 ``` --- diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index 4b04c7c..f8a6c6d 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -17,6 +17,7 @@ - **Гибкая параметризация CLI (chainCount, nestingDepth, repeats, warmup, сценарий, формат)** - **Автоматический матричный запуск для наборов параметров** - **Статистика: среднее, медиана, stddev, min, max для каждого сценария** +- **Память: memory_diff_kb (итоговая разница), delta_peak_kb (максимальный рост), peak_rss_kb (абсолютный пик)** - **Вывод в таблицу, CSV или JSON** - **Прогревочные запуски до замера времени для стабильности** @@ -61,8 +62,8 @@ #### Пример вывода (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us -ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000" +benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us,memory_diff_kb,delta_peak_kb,peak_rss_kb +ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000",-64,0,200064 ``` --- From 553dbb6539e401120da17ad3f677a224da199b92 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 14:44:12 +0300 Subject: [PATCH 06/32] refactor(benchmarks): introduce DIAdapter abstraction, migrate all scenarios to use DIAdapter --- benchmark_cherrypick/bin/main.dart | 99 ++++++++++++++++--- .../lib/async_chain_benchmark.dart | 14 +-- .../lib/cherrypick_benchmark.dart | 20 ++-- .../lib/complex_bindings_benchmark.dart | 40 ++++---- benchmark_cherrypick/lib/di_adapter.dart | 78 +++++++++++++++ .../lib/scope_override_benchmark.dart | 25 ++--- 6 files changed, 213 insertions(+), 63 deletions(-) create mode 100644 benchmark_cherrypick/lib/di_adapter.dart diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 026b0c1..12c5185 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -1,14 +1,19 @@ import 'package:benchmark_cherrypick/cherrypick_benchmark.dart'; import 'package:benchmark_cherrypick/complex_bindings_benchmark.dart'; import 'package:benchmark_cherrypick/async_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/di_adapter.dart'; import 'package:benchmark_cherrypick/scope_override_benchmark.dart'; import 'package:args/args.dart'; +import 'dart:io'; +import 'dart:math'; Future main(List args) async { final parser = ArgParser() ..addOption('benchmark', abbr: 'b', help: 'Benchmark name (register, chain_singleton, chain_factory, named, override, async_chain, all)', defaultsTo: 'all') ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts (используется в chain_singleton/factory)', defaultsTo: '100') ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths (используется в chain_singleton/factory)', defaultsTo: '100') + ..addOption('repeat', abbr: 'r', help: 'Repeats for each run (statistical run, >=2)', defaultsTo: '5') + ..addOption('warmup', abbr: 'w', help: 'Warmup runs before timing', defaultsTo: '2') ..addOption('format', abbr: 'f', help: 'Output format (pretty, csv, json)', defaultsTo: 'pretty') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); @@ -17,66 +22,116 @@ Future main(List args) async { if (result['help'] == true) { print('Dart DI benchmarks'); print(parser.usage); + print('\nExamples:\n' + ' dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv\n' + ' dart run bin/main.dart --benchmark=named\n' + 'Extra: --repeat=7 --warmup=3\n'); return; } + final di = CherrypickDIAdapter(); + final benchmark = result['benchmark'] as String; final format = result['format'] as String; final chainCounts = _parseIntList(result['chainCount'] as String); final nestDepths = _parseIntList(result['nestingDepth'] as String); + final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 5; + final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 2; final results = >[]; - void addResult(String name, int? chainCount, int? nestingDepth, num elapsed) { + void addResult( + String name, + int? chainCount, + int? nestingDepth, + List timings, + int? memoryDiffKb, + int? deltaPeakKb, + int? peakRssKb, + ) { + 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 = sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length); results.add({ 'benchmark': name, 'chainCount': chainCount, 'nestingDepth': nestingDepth, - 'elapsed_us': elapsed.round() + 'mean_us': mean.round(), + 'median_us': median.round(), + 'stddev_us': stddev.round(), + 'min_us': minVal.round(), + 'max_us': maxVal.round(), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.round()).toList(), + 'memory_diff_kb': memoryDiffKb, + 'delta_peak_kb': deltaPeakKb, + 'peak_rss_kb': peakRssKb, }); } - Future runAndCollect(String name, Future Function() fn, {int? chainCount, int? nestingDepth}) async { - final elapsed = await fn(); - addResult(name, chainCount, nestingDepth, elapsed); + Future runAndCollect( + String name, + Future Function() fn, { + int? chainCount, + int? nestingDepth, + }) async { + for (int i = 0; i < warmups; i++) { + await fn(); + } + final timings = []; + final rssValues = []; + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + timings.add(await fn()); + rssValues.add(ProcessInfo.currentRss); + } + final memAfter = ProcessInfo.currentRss; + final memDiffKB = ((memAfter - memBefore) / 1024).round(); + final peakRss = [...rssValues, memBefore].reduce(max); + final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + addResult(name, chainCount, nestingDepth, timings, memDiffKB, deltaPeakKb, (peakRss/1024).round()); } if (benchmark == 'all' || benchmark == 'register') { await runAndCollect('RegisterAndResolve', () async { - return _captureReport(RegisterAndResolveBenchmark().report); + return _captureReport(RegisterAndResolveBenchmark(di).report); }); } if (benchmark == 'all' || benchmark == 'chain_singleton') { - for (final c in chainCounts) { + for (final c in chainCounts) { for (final d in nestDepths) { await runAndCollect('ChainSingleton', () async { - return _captureReport(() => ChainSingletonBenchmark(chainCount: c, nestingDepth: d).report()); + return _captureReport(() => ChainSingletonBenchmark(di,chainCount: c, nestingDepth: d).report()); }, chainCount: c, nestingDepth: d); } } } if (benchmark == 'all' || benchmark == 'chain_factory') { - for (final c in chainCounts) { + for (final c in chainCounts) { for (final d in nestDepths) { await runAndCollect('ChainFactory', () async { - return _captureReport(() => ChainFactoryBenchmark(chainCount: c, nestingDepth: d).report()); + return _captureReport(() => ChainFactoryBenchmark(di, chainCount: c, nestingDepth: d).report()); }, chainCount: c, nestingDepth: d); } } } if (benchmark == 'all' || benchmark == 'named') { await runAndCollect('NamedResolve', () async { - return _captureReport(NamedResolveBenchmark().report); + return _captureReport(NamedResolveBenchmark(di).report); }); } if (benchmark == 'all' || benchmark == 'override') { await runAndCollect('ScopeOverride', () async { - return _captureReport(ScopeOverrideBenchmark().report); + return _captureReport(ScopeOverrideBenchmark(di).report); }); } if (benchmark == 'all' || benchmark == 'async_chain') { await runAndCollect('AsyncChain', () async { - return _captureReportAsync(AsyncChainBenchmark().report); + return _captureReportAsync(AsyncChainBenchmark(di).report); }); } @@ -106,16 +161,28 @@ Future _captureReportAsync(Future Function() fn) async { } String _toPretty(List> rows) { - final keys = ['benchmark','chainCount','nestingDepth','elapsed_us']; + final keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; final header = keys.join('\t'); final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); return ([header] + lines).join('\n'); } String _toCsv(List> rows) { - final keys = ['benchmark','chainCount','nestingDepth','elapsed_us']; + final keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; final header = keys.join(','); - final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join(',')).toList(); + final lines = rows.map((r) => + keys.map((k) { + final v = r[k]; + if (v is List) return '"${v.join(';')}"'; + return (v ?? '').toString(); + }).join(',') + ).toList(); return ([header] + lines).join('\n'); } diff --git a/benchmark_cherrypick/lib/async_chain_benchmark.dart b/benchmark_cherrypick/lib/async_chain_benchmark.dart index 7b27184..636f8ba 100644 --- a/benchmark_cherrypick/lib/async_chain_benchmark.dart +++ b/benchmark_cherrypick/lib/async_chain_benchmark.dart @@ -1,7 +1,8 @@ // ignore: depend_on_referenced_packages +import 'package:benchmark_cherrypick/di_adapter.dart'; +// ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; -import 'benchmark_utils.dart'; class AsyncA {} @@ -30,21 +31,22 @@ class AsyncChainModule extends Module { } } -class AsyncChainBenchmark extends AsyncBenchmarkBase with BenchmarkWithScope { - AsyncChainBenchmark() : super('AsyncChain (A->B->C, async)'); +class AsyncChainBenchmark extends AsyncBenchmarkBase { + final DIAdapter di; + AsyncChainBenchmark(this.di) : super('AsyncChain (A->B->C, async)'); @override Future setup() async { - setupScope([AsyncChainModule()]); + di.setupModules([AsyncChainModule()]); } @override Future teardown() async { - teardownScope(); + di.teardown(); } @override Future run() async { - await scope.resolveAsync(); + await di.resolveAsync(); } } diff --git a/benchmark_cherrypick/lib/cherrypick_benchmark.dart b/benchmark_cherrypick/lib/cherrypick_benchmark.dart index 87c53f0..caeb56d 100644 --- a/benchmark_cherrypick/lib/cherrypick_benchmark.dart +++ b/benchmark_cherrypick/lib/cherrypick_benchmark.dart @@ -1,4 +1,6 @@ // ignore: depend_on_referenced_packages +import 'package:benchmark_cherrypick/di_adapter.dart'; +// ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; @@ -13,26 +15,20 @@ class AppModule extends Module { class FooService {} class RegisterAndResolveBenchmark extends BenchmarkBase { - RegisterAndResolveBenchmark() : super('RegisterAndResolve'); - late final Scope scope; + final DIAdapter di; + + RegisterAndResolveBenchmark(this.di) : super('RegisterAndResolve'); @override void setup() { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - scope = CherryPick.openRootScope(); - scope.installModules([AppModule()]); + di.setupModules([AppModule()]); } @override void run() { - scope.resolve(); + di.resolve(); } @override - void teardown() => CherryPick.closeRootScope(); -} - -void main() { - RegisterAndResolveBenchmark().report(); + void teardown() => di.teardown(); } diff --git a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart index b2754e8..51a4f3f 100644 --- a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart +++ b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart @@ -1,7 +1,8 @@ // ignore: depend_on_referenced_packages +import 'package:benchmark_cherrypick/di_adapter.dart'; +// ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; -import 'benchmark_utils.dart'; // === DI graph: A -> B -> C (singleton) === abstract class Service { @@ -59,11 +60,13 @@ class ChainSingletonModule extends Module { } } -class ChainSingletonBenchmark extends BenchmarkBase with BenchmarkWithScope { +class ChainSingletonBenchmark extends BenchmarkBase { + final DIAdapter di; final int chainCount; final int nestingDepth; - ChainSingletonBenchmark({ + ChainSingletonBenchmark( + this.di, { this.chainCount = 1, this.nestingDepth = 3, }) : super( @@ -73,7 +76,7 @@ class ChainSingletonBenchmark extends BenchmarkBase with BenchmarkWithScope { @override void setup() { - setupScope([ + di.setupModules([ ChainSingletonModule( chainCount: chainCount, nestingDepth: nestingDepth, @@ -82,12 +85,12 @@ class ChainSingletonBenchmark extends BenchmarkBase with BenchmarkWithScope { } @override - void teardown() => teardownScope(); + void teardown() => di.teardown(); @override void run() { final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - scope.resolve(named: serviceName); + di.resolve(named: serviceName); } } @@ -129,11 +132,13 @@ class ChainFactoryModule extends Module { } } -class ChainFactoryBenchmark extends BenchmarkBase with BenchmarkWithScope { +class ChainFactoryBenchmark extends BenchmarkBase { + final DIAdapter di; final int chainCount; final int nestingDepth; - ChainFactoryBenchmark({ + ChainFactoryBenchmark( + this.di, { this.chainCount = 1, this.nestingDepth = 3, }) : super( @@ -143,7 +148,7 @@ class ChainFactoryBenchmark extends BenchmarkBase with BenchmarkWithScope { @override void setup() { - setupScope([ + di.setupModules([ ChainFactoryModule( chainCount: chainCount, nestingDepth: nestingDepth, @@ -152,12 +157,12 @@ class ChainFactoryBenchmark extends BenchmarkBase with BenchmarkWithScope { } @override - void teardown() => teardownScope(); + void teardown() => di.teardown(); @override void run() { final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - scope.resolve(named: serviceName); + di.resolve(named: serviceName); } } @@ -174,20 +179,21 @@ class NamedModule extends Module { } } -class NamedResolveBenchmark extends BenchmarkBase with BenchmarkWithScope { - NamedResolveBenchmark() : super('NamedResolve (by name)'); +class NamedResolveBenchmark extends BenchmarkBase { + final DIAdapter di; + + NamedResolveBenchmark(this.di) : super('NamedResolve (by name)'); @override void setup() { - setupScope([NamedModule()]); + di.setupModules([NamedModule()]); } @override - void teardown() => teardownScope(); + void teardown() => di.teardown(); @override void run() { - // Switch name for comparison - scope.resolve(named: 'impl2'); + di.resolve(named: 'impl2'); } } diff --git a/benchmark_cherrypick/lib/di_adapter.dart b/benchmark_cherrypick/lib/di_adapter.dart new file mode 100644 index 0000000..aed5ecc --- /dev/null +++ b/benchmark_cherrypick/lib/di_adapter.dart @@ -0,0 +1,78 @@ +import 'package:cherrypick/cherrypick.dart'; + +abstract class DIAdapter { + void setupModules(List modules); + T resolve({String? named}); + Future resolveAsync({String? named}); + void teardown(); + DIAdapter openSubScope(String name); +} + +class CherrypickDIAdapter implements DIAdapter { + Scope? _scope; + + @override + void setupModules(List modules) { + _scope = CherryPick.openRootScope(); + _scope!.installModules(modules); + } + + @override + T resolve({String? named}) { + return named == null + ? _scope!.resolve() + : _scope!.resolve(named: named); + } + + @override + Future resolveAsync({String? named}) async { + return named == null + ? await _scope!.resolveAsync() + : await _scope!.resolveAsync(named: named); + } + + @override + void teardown() { + CherryPick.closeRootScope(); + _scope = null; + } + + @override + CherrypickDIAdapter openSubScope(String name) { + final sub = _scope!.openSubScope(name); + return _CherrypickSubScopeAdapter(sub); + } +} + +class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { + final Scope _subScope; + _CherrypickSubScopeAdapter(this._subScope); + @override + void setupModules(List modules) { + _subScope.installModules(modules); + } + + @override + T resolve({String? named}) { + return named == null + ? _subScope.resolve() + : _subScope.resolve(named: named); + } + + @override + Future resolveAsync({String? named}) async { + return named == null + ? await _subScope.resolveAsync() + : await _subScope.resolveAsync(named: named); + } + + @override + void teardown() { + // subScope teardown убирать отдельно не требуется + } + + @override + CherrypickDIAdapter openSubScope(String name) { + return _CherrypickSubScopeAdapter(_subScope.openSubScope(name)); + } +} diff --git a/benchmark_cherrypick/lib/scope_override_benchmark.dart b/benchmark_cherrypick/lib/scope_override_benchmark.dart index 868ee69..e976c1d 100644 --- a/benchmark_cherrypick/lib/scope_override_benchmark.dart +++ b/benchmark_cherrypick/lib/scope_override_benchmark.dart @@ -1,7 +1,8 @@ // ignore: depend_on_referenced_packages +import 'package:benchmark_cherrypick/di_adapter.dart'; +// ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:cherrypick/cherrypick.dart'; -import 'benchmark_utils.dart'; class Shared {} @@ -23,26 +24,26 @@ class ChildOverrideModule extends Module { } } -class ScopeOverrideBenchmark extends BenchmarkBase with BenchmarkWithScope { - ScopeOverrideBenchmark() : super('ScopeOverride (child overrides parent)'); - late Scope child; +class ScopeOverrideBenchmark extends BenchmarkBase { + final DIAdapter di; + late DIAdapter childDi; + + ScopeOverrideBenchmark(this.di) : super('ScopeOverride (child overrides parent)'); @override void setup() { - setupScope([ParentModule()]); - child = scope.openSubScope('child'); - child.installModules([ChildOverrideModule()]); + di.setupModules([ParentModule()]); + childDi = di.openSubScope('child'); + childDi.setupModules([ChildOverrideModule()]); } @override - void teardown() { - teardownScope(); - } + void teardown() => di.teardown(); @override void run() { - // Должен возвращать ChildImpl, а не ParentImpl - final resolved = child.resolve(); + // Should return ChildImpl, not ParentImpl + final resolved = childDi.resolve(); assert(resolved is ChildImpl); } } From b27a7df161c40b88455d77b9e9901923d0a8fd01 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 16:21:31 +0300 Subject: [PATCH 07/32] refactor(structure): move benchmarks, scenarios, adapters, utils to dedicated folders; update imports/project layout --- benchmark_cherrypick/analysis_options.yaml | 1 + benchmark_cherrypick/bin/main.dart | 12 +- .../lib/async_chain_benchmark.dart | 52 ----- .../lib/benchmarks/async_chain_benchmark.dart | 23 ++ .../benchmarks/chain_factory_benchmark.dart | 38 ++++ .../benchmarks/chain_singleton_benchmark.dart | 38 ++++ .../benchmarks/named_resolve_benchmark.dart | 22 ++ .../register_and_resolve_benchmark.dart} | 17 +- .../benchmarks/scope_override_benchmark.dart | 29 +++ .../lib/complex_bindings_benchmark.dart | 199 ------------------ .../cherrypick_adapter.dart} | 9 +- .../lib/di_adapters/di_adapter.dart | 9 + .../lib/scenarios/app_module.dart | 9 + .../lib/scenarios/async_chain_module.dart | 20 ++ .../lib/scenarios/chain_factory_module.dart | 42 ++++ .../lib/scenarios/chain_singleton_module.dart | 42 ++++ .../lib/scenarios/child_impl.dart | 2 + .../lib/scenarios/child_override_module.dart | 10 + .../lib/scenarios/foo_service.dart | 1 + .../lib/scenarios/named_module.dart | 12 ++ .../lib/scenarios/parent_impl.dart | 2 + .../lib/scenarios/parent_module.dart | 10 + .../lib/scenarios/service.dart | 5 + .../lib/scenarios/service_impl.dart | 5 + .../lib/scenarios/shared.dart | 1 + .../lib/scope_override_benchmark.dart | 49 ----- .../lib/{ => utils}/benchmark_utils.dart | 0 27 files changed, 332 insertions(+), 327 deletions(-) delete mode 100644 benchmark_cherrypick/lib/async_chain_benchmark.dart create mode 100644 benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart create mode 100644 benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart create mode 100644 benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart create mode 100644 benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart rename benchmark_cherrypick/lib/{cherrypick_benchmark.dart => benchmarks/register_and_resolve_benchmark.dart} (51%) create mode 100644 benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/complex_bindings_benchmark.dart rename benchmark_cherrypick/lib/{di_adapter.dart => di_adapters/cherrypick_adapter.dart} (88%) create mode 100644 benchmark_cherrypick/lib/di_adapters/di_adapter.dart create mode 100644 benchmark_cherrypick/lib/scenarios/app_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/async_chain_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/chain_factory_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/child_impl.dart create mode 100644 benchmark_cherrypick/lib/scenarios/child_override_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/foo_service.dart create mode 100644 benchmark_cherrypick/lib/scenarios/named_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/parent_impl.dart create mode 100644 benchmark_cherrypick/lib/scenarios/parent_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/service.dart create mode 100644 benchmark_cherrypick/lib/scenarios/service_impl.dart create mode 100644 benchmark_cherrypick/lib/scenarios/shared.dart delete mode 100644 benchmark_cherrypick/lib/scope_override_benchmark.dart rename benchmark_cherrypick/lib/{ => utils}/benchmark_utils.dart (100%) diff --git a/benchmark_cherrypick/analysis_options.yaml b/benchmark_cherrypick/analysis_options.yaml index 95b8ade..6bd1e89 100644 --- a/benchmark_cherrypick/analysis_options.yaml +++ b/benchmark_cherrypick/analysis_options.yaml @@ -15,6 +15,7 @@ include: package:lints/recommended.yaml analyzer: errors: deprecated_member_use: ignore + depend_on_referenced_packages: ignore # Uncomment the following section to specify additional rules. diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 12c5185..2112cf4 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -1,8 +1,10 @@ -import 'package:benchmark_cherrypick/cherrypick_benchmark.dart'; -import 'package:benchmark_cherrypick/complex_bindings_benchmark.dart'; -import 'package:benchmark_cherrypick/async_chain_benchmark.dart'; -import 'package:benchmark_cherrypick/di_adapter.dart'; -import 'package:benchmark_cherrypick/scope_override_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/register_and_resolve_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/chain_singleton_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/chain_factory_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/named_resolve_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/scope_override_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/async_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; import 'package:args/args.dart'; import 'dart:io'; import 'dart:math'; diff --git a/benchmark_cherrypick/lib/async_chain_benchmark.dart b/benchmark_cherrypick/lib/async_chain_benchmark.dart deleted file mode 100644 index 636f8ba..0000000 --- a/benchmark_cherrypick/lib/async_chain_benchmark.dart +++ /dev/null @@ -1,52 +0,0 @@ -// ignore: depend_on_referenced_packages -import 'package:benchmark_cherrypick/di_adapter.dart'; -// ignore: depend_on_referenced_packages -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:cherrypick/cherrypick.dart'; - -class AsyncA {} - -class AsyncB { - final AsyncA a; - AsyncB(this.a); -} - -class AsyncC { - final AsyncB b; - AsyncC(this.b); -} - -class AsyncChainModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvideAsync(() async => AsyncA()).singleton(); - bind() - .toProvideAsync( - () async => AsyncB(await currentScope.resolveAsync())) - .singleton(); - bind() - .toProvideAsync( - () async => AsyncC(await currentScope.resolveAsync())) - .singleton(); - } -} - -class AsyncChainBenchmark extends AsyncBenchmarkBase { - final DIAdapter di; - AsyncChainBenchmark(this.di) : super('AsyncChain (A->B->C, async)'); - - @override - Future setup() async { - di.setupModules([AsyncChainModule()]); - } - - @override - Future teardown() async { - di.teardown(); - } - - @override - Future run() async { - await di.resolveAsync(); - } -} diff --git a/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart new file mode 100644 index 0000000..68b8ef9 --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart @@ -0,0 +1,23 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/async_chain_module.dart'; + +class AsyncChainBenchmark extends AsyncBenchmarkBase { + final DIAdapter di; + AsyncChainBenchmark(this.di) : super('AsyncChain (A->B->C, async)'); + + @override + Future setup() async { + di.setupModules([AsyncChainModule()]); + } + + @override + Future teardown() async { + di.teardown(); + } + + @override + Future run() async { + await di.resolveAsync(); + } +} diff --git a/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart new file mode 100644 index 0000000..ddb2b0d --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart @@ -0,0 +1,38 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/chain_factory_module.dart'; +import 'package:benchmark_cherrypick/scenarios/service.dart'; + +class ChainFactoryBenchmark extends BenchmarkBase { + final DIAdapter di; + final int chainCount; + final int nestingDepth; + + ChainFactoryBenchmark( + this.di, { + this.chainCount = 1, + this.nestingDepth = 3, + }) : super( + 'ChainFactory (A->B->C, factory). ' + 'C/D = $chainCount/$nestingDepth. ', + ); + + @override + void setup() { + di.setupModules([ + ChainFactoryModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + ), + ]); + } + + @override + void teardown() => di.teardown(); + + @override + void run() { + final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; + di.resolve(named: serviceName); + } +} diff --git a/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart new file mode 100644 index 0000000..a1ba257 --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart @@ -0,0 +1,38 @@ +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_harness/benchmark_harness.dart'; +import '../scenarios/chain_singleton_module.dart'; +import '../scenarios/service.dart'; + +class ChainSingletonBenchmark extends BenchmarkBase { + final DIAdapter di; + final int chainCount; + final int nestingDepth; + + ChainSingletonBenchmark( + this.di, { + this.chainCount = 1, + this.nestingDepth = 3, + }) : super( + 'ChainSingleton (A->B->C, singleton). ' + 'C/D = $chainCount/$nestingDepth. ', + ); + + @override + void setup() { + di.setupModules([ + ChainSingletonModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + ), + ]); + } + + @override + void teardown() => di.teardown(); + + @override + void run() { + final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; + di.resolve(named: serviceName); + } +} diff --git a/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart new file mode 100644 index 0000000..7a7b8a7 --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart @@ -0,0 +1,22 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/named_module.dart'; + +class NamedResolveBenchmark extends BenchmarkBase { + final DIAdapter di; + + NamedResolveBenchmark(this.di) : super('NamedResolve (by name)'); + + @override + void setup() { + di.setupModules([NamedModule()]); + } + + @override + void teardown() => di.teardown(); + + @override + void run() { + di.resolve(named: 'impl2'); + } +} diff --git a/benchmark_cherrypick/lib/cherrypick_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart similarity index 51% rename from benchmark_cherrypick/lib/cherrypick_benchmark.dart rename to benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart index caeb56d..a2442fe 100644 --- a/benchmark_cherrypick/lib/cherrypick_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart @@ -1,18 +1,7 @@ -// ignore: depend_on_referenced_packages -import 'package:benchmark_cherrypick/di_adapter.dart'; -// ignore: depend_on_referenced_packages import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:cherrypick/cherrypick.dart'; - -class AppModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => FooService()); - } -} - -// Dummy service for DI -class FooService {} +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/app_module.dart'; +import 'package:benchmark_cherrypick/scenarios/foo_service.dart'; class RegisterAndResolveBenchmark extends BenchmarkBase { final DIAdapter di; diff --git a/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart new file mode 100644 index 0000000..4c96a3b --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart @@ -0,0 +1,29 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/parent_module.dart'; +import 'package:benchmark_cherrypick/scenarios/child_override_module.dart'; +import 'package:benchmark_cherrypick/scenarios/shared.dart'; +import 'package:benchmark_cherrypick/scenarios/child_impl.dart'; + +class ScopeOverrideBenchmark extends BenchmarkBase { + final DIAdapter di; + late DIAdapter childDi; + + ScopeOverrideBenchmark(this.di) : super('ScopeOverride (child overrides parent)'); + + @override + void setup() { + di.setupModules([ParentModule()]); + childDi = di.openSubScope('child'); + childDi.setupModules([ChildOverrideModule()]); + } + + @override + void teardown() => di.teardown(); + + @override + void run() { + final resolved = childDi.resolve(); + assert(resolved is ChildImpl); + } +} diff --git a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart deleted file mode 100644 index 51a4f3f..0000000 --- a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart +++ /dev/null @@ -1,199 +0,0 @@ -// ignore: depend_on_referenced_packages -import 'package:benchmark_cherrypick/di_adapter.dart'; -// ignore: depend_on_referenced_packages -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:cherrypick/cherrypick.dart'; - -// === DI graph: A -> B -> C (singleton) === -abstract class Service { - final dynamic value; - final Service? dependency; - - Service({ - required this.value, - this.dependency, - }); -} - -class ServiceImpl extends Service { - ServiceImpl({ - required super.value, - super.dependency, - }); -} - -class ChainSingletonModule extends Module { - // количество независимых цепочек - final int chainCount; - - // глубина вложенности - final int nestingDepth; - - ChainSingletonModule({ - required this.chainCount, - required this.nestingDepth, - }); - - @override - void builder(Scope currentScope) { - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - - final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; - final depName = '${chain.toString()}_${level.toString()}'; - - bind() - .toProvide( - () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) - .withName(depName) - .singleton(); - } - } - } -} - -class ChainSingletonBenchmark extends BenchmarkBase { - final DIAdapter di; - final int chainCount; - final int nestingDepth; - - ChainSingletonBenchmark( - this.di, { - this.chainCount = 1, - this.nestingDepth = 3, - }) : super( - 'ChainSingleton (A->B->C, singleton). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - - @override - void setup() { - di.setupModules([ - ChainSingletonModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - ), - ]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - di.resolve(named: serviceName); - } -} - -// === DI graph: A -> B -> C (factory/no singleton) === -class ChainFactoryModule extends Module { - // количество независимых цепочек - final int chainCount; - - // глубина вложенности - final int nestingDepth; - - ChainFactoryModule({ - required this.chainCount, - required this.nestingDepth, - }); - - @override - void builder(Scope currentScope) { - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - - final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; - final depName = '${chain.toString()}_${level.toString()}'; - - bind() - .toProvide( - () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) - .withName(depName); - } - } - } -} - -class ChainFactoryBenchmark extends BenchmarkBase { - final DIAdapter di; - final int chainCount; - final int nestingDepth; - - ChainFactoryBenchmark( - this.di, { - this.chainCount = 1, - this.nestingDepth = 3, - }) : super( - 'ChainFactory (A->B->C, factory). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - - @override - void setup() { - di.setupModules([ - ChainFactoryModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - ), - ]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - di.resolve(named: serviceName); - } -} - -// === Named bindings: Multiple implementations === -class Impl1 {} - -class Impl2 {} - -class NamedModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => Impl1()).withName('impl1'); - bind().toProvide(() => Impl2()).withName('impl2'); - } -} - -class NamedResolveBenchmark extends BenchmarkBase { - final DIAdapter di; - - NamedResolveBenchmark(this.di) : super('NamedResolve (by name)'); - - @override - void setup() { - di.setupModules([NamedModule()]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - di.resolve(named: 'impl2'); - } -} diff --git a/benchmark_cherrypick/lib/di_adapter.dart b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart similarity index 88% rename from benchmark_cherrypick/lib/di_adapter.dart rename to benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart index aed5ecc..e9489ec 100644 --- a/benchmark_cherrypick/lib/di_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart @@ -1,12 +1,5 @@ import 'package:cherrypick/cherrypick.dart'; - -abstract class DIAdapter { - void setupModules(List modules); - T resolve({String? named}); - Future resolveAsync({String? named}); - void teardown(); - DIAdapter openSubScope(String name); -} +import 'di_adapter.dart'; class CherrypickDIAdapter implements DIAdapter { Scope? _scope; diff --git a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart new file mode 100644 index 0000000..de818e3 --- /dev/null +++ b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart @@ -0,0 +1,9 @@ +import 'package:cherrypick/cherrypick.dart'; + +abstract class DIAdapter { + void setupModules(List modules); + T resolve({String? named}); + Future resolveAsync({String? named}); + void teardown(); + DIAdapter openSubScope(String name); +} diff --git a/benchmark_cherrypick/lib/scenarios/app_module.dart b/benchmark_cherrypick/lib/scenarios/app_module.dart new file mode 100644 index 0000000..fa68fd9 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/app_module.dart @@ -0,0 +1,9 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'foo_service.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => FooService()); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/async_chain_module.dart b/benchmark_cherrypick/lib/scenarios/async_chain_module.dart new file mode 100644 index 0000000..e0044e9 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/async_chain_module.dart @@ -0,0 +1,20 @@ +import 'package:cherrypick/cherrypick.dart'; + +class AsyncA {} +class AsyncB { + final AsyncA a; + AsyncB(this.a); +} +class AsyncC { + final AsyncB b; + AsyncC(this.b); +} + +class AsyncChainModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvideAsync(() async => AsyncA()).singleton(); + bind().toProvideAsync(() async => AsyncB(await currentScope.resolveAsync())).singleton(); + bind().toProvideAsync(() async => AsyncC(await currentScope.resolveAsync())).singleton(); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart b/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart new file mode 100644 index 0000000..ade4ccf --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart @@ -0,0 +1,42 @@ +// === DI graph: A -> B -> C (factory/no singleton) === +import 'package:cherrypick/cherrypick.dart'; + +import 'service.dart'; +import 'service_impl.dart'; + +class ChainFactoryModule extends Module { + // количество независимых цепочек + final int chainCount; + + // глубина вложенности + final int nestingDepth; + + ChainFactoryModule({ + required this.chainCount, + required this.nestingDepth, + }); + + @override + void builder(Scope currentScope) { + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + + final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; + final depName = '${chain.toString()}_${level.toString()}'; + + bind() + .toProvide( + () => ServiceImpl( + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) + .withName(depName); + } + } + } +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart b/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart new file mode 100644 index 0000000..72fa5ca --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart @@ -0,0 +1,42 @@ +import 'package:cherrypick/cherrypick.dart'; + +import 'service.dart'; +import 'service_impl.dart'; + +class ChainSingletonModule extends Module { + // количество независимых цепочек + final int chainCount; + + // глубина вложенности + final int nestingDepth; + + ChainSingletonModule({ + required this.chainCount, + required this.nestingDepth, + }); + + @override + void builder(Scope currentScope) { + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + + final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; + final depName = '${chain.toString()}_${level.toString()}'; + + bind() + .toProvide( + () => ServiceImpl( + value: depName, + dependency: currentScope.tryResolve( + named: prevDepName, + ), + ), + ) + .withName(depName) + .singleton(); + } + } + } +} diff --git a/benchmark_cherrypick/lib/scenarios/child_impl.dart b/benchmark_cherrypick/lib/scenarios/child_impl.dart new file mode 100644 index 0000000..e564b60 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/child_impl.dart @@ -0,0 +1,2 @@ +import 'shared.dart'; +class ChildImpl extends Shared {} diff --git a/benchmark_cherrypick/lib/scenarios/child_override_module.dart b/benchmark_cherrypick/lib/scenarios/child_override_module.dart new file mode 100644 index 0000000..3ace258 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/child_override_module.dart @@ -0,0 +1,10 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'child_impl.dart'; +import 'shared.dart'; + +class ChildOverrideModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => ChildImpl()).singleton(); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/foo_service.dart b/benchmark_cherrypick/lib/scenarios/foo_service.dart new file mode 100644 index 0000000..d72c805 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/foo_service.dart @@ -0,0 +1 @@ +class FooService {} diff --git a/benchmark_cherrypick/lib/scenarios/named_module.dart b/benchmark_cherrypick/lib/scenarios/named_module.dart new file mode 100644 index 0000000..a0708c0 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/named_module.dart @@ -0,0 +1,12 @@ +import 'package:cherrypick/cherrypick.dart'; + +class Impl1 {} +class Impl2 {} + +class NamedModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => Impl1()).withName('impl1'); + bind().toProvide(() => Impl2()).withName('impl2'); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/parent_impl.dart b/benchmark_cherrypick/lib/scenarios/parent_impl.dart new file mode 100644 index 0000000..e6a5f40 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/parent_impl.dart @@ -0,0 +1,2 @@ +import 'shared.dart'; +class ParentImpl extends Shared {} diff --git a/benchmark_cherrypick/lib/scenarios/parent_module.dart b/benchmark_cherrypick/lib/scenarios/parent_module.dart new file mode 100644 index 0000000..2e3d83f --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/parent_module.dart @@ -0,0 +1,10 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'parent_impl.dart'; +import 'shared.dart'; + +class ParentModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => ParentImpl()).singleton(); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/service.dart b/benchmark_cherrypick/lib/scenarios/service.dart new file mode 100644 index 0000000..065646b --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/service.dart @@ -0,0 +1,5 @@ +abstract class Service { + final dynamic value; + final Service? dependency; + Service({required this.value, this.dependency}); +} diff --git a/benchmark_cherrypick/lib/scenarios/service_impl.dart b/benchmark_cherrypick/lib/scenarios/service_impl.dart new file mode 100644 index 0000000..58ee09b --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/service_impl.dart @@ -0,0 +1,5 @@ +import 'service.dart'; + +class ServiceImpl extends Service { + ServiceImpl({required super.value, super.dependency}); +} diff --git a/benchmark_cherrypick/lib/scenarios/shared.dart b/benchmark_cherrypick/lib/scenarios/shared.dart new file mode 100644 index 0000000..f3aa38b --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/shared.dart @@ -0,0 +1 @@ +class Shared {} diff --git a/benchmark_cherrypick/lib/scope_override_benchmark.dart b/benchmark_cherrypick/lib/scope_override_benchmark.dart deleted file mode 100644 index e976c1d..0000000 --- a/benchmark_cherrypick/lib/scope_override_benchmark.dart +++ /dev/null @@ -1,49 +0,0 @@ -// ignore: depend_on_referenced_packages -import 'package:benchmark_cherrypick/di_adapter.dart'; -// ignore: depend_on_referenced_packages -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:cherrypick/cherrypick.dart'; - -class Shared {} - -class ParentImpl extends Shared {} - -class ChildImpl extends Shared {} - -class ParentModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => ParentImpl()).singleton(); - } -} - -class ChildOverrideModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => ChildImpl()).singleton(); - } -} - -class ScopeOverrideBenchmark extends BenchmarkBase { - final DIAdapter di; - late DIAdapter childDi; - - ScopeOverrideBenchmark(this.di) : super('ScopeOverride (child overrides parent)'); - - @override - void setup() { - di.setupModules([ParentModule()]); - childDi = di.openSubScope('child'); - childDi.setupModules([ChildOverrideModule()]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - // Should return ChildImpl, not ParentImpl - final resolved = childDi.resolve(); - assert(resolved is ChildImpl); - } -} diff --git a/benchmark_cherrypick/lib/benchmark_utils.dart b/benchmark_cherrypick/lib/utils/benchmark_utils.dart similarity index 100% rename from benchmark_cherrypick/lib/benchmark_utils.dart rename to benchmark_cherrypick/lib/utils/benchmark_utils.dart From 3a75bd5b284d9abcfbe5ff678e55cd15d02e907a Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 17:01:55 +0300 Subject: [PATCH 08/32] feat(benchmark): add UniversalScenario enum and extend UniversalChainModule to support chain, register, named, override, async scenarios --- benchmark_cherrypick/bin/main.dart | 102 ++++++------------ .../benchmarks/universal_chain_benchmark.dart | 40 +++++++ .../lib/scenarios/universal_chain_module.dart | 60 +++++++++++ .../lib/scenarios/universal_service.dart | 10 ++ 4 files changed, 142 insertions(+), 70 deletions(-) create mode 100644 benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart create mode 100644 benchmark_cherrypick/lib/scenarios/universal_chain_module.dart create mode 100644 benchmark_cherrypick/lib/scenarios/universal_service.dart diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 2112cf4..2d045d9 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -1,58 +1,52 @@ -import 'package:benchmark_cherrypick/benchmarks/register_and_resolve_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/chain_singleton_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/chain_factory_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/named_resolve_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/scope_override_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/async_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:args/args.dart'; import 'dart:io'; import 'dart:math'; Future main(List args) async { final parser = ArgParser() - ..addOption('benchmark', abbr: 'b', help: 'Benchmark name (register, chain_singleton, chain_factory, named, override, async_chain, all)', defaultsTo: 'all') - ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts (используется в chain_singleton/factory)', defaultsTo: '100') - ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths (используется в chain_singleton/factory)', defaultsTo: '100') - ..addOption('repeat', abbr: 'r', help: 'Repeats for each run (statistical run, >=2)', defaultsTo: '5') - ..addOption('warmup', abbr: 'w', help: 'Warmup runs before timing', defaultsTo: '2') + ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts', defaultsTo: '10') + ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths', defaultsTo: '5') + ..addOption('mode', abbr: 'm', help: 'Mode (singletonStrategy,factoryStrategy,asyncStrategy)', defaultsTo: 'singletonStrategy') + ..addOption('repeat', abbr: 'r', help: 'Repeats for each run (>=2)', defaultsTo: '2') + ..addOption('warmup', abbr: 'w', help: 'Warmup runs', defaultsTo: '1') ..addOption('format', abbr: 'f', help: 'Output format (pretty, csv, json)', defaultsTo: 'pretty') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); final result = parser.parse(args); if (result['help'] == true) { - print('Dart DI benchmarks'); + print('UniversalChainBenchmark'); print(parser.usage); - print('\nExamples:\n' - ' dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --format=csv\n' - ' dart run bin/main.dart --benchmark=named\n' - 'Extra: --repeat=7 --warmup=3\n'); return; } - final di = CherrypickDIAdapter(); - - final benchmark = result['benchmark'] as String; - final format = result['format'] as String; - final chainCounts = _parseIntList(result['chainCount'] as String); final nestDepths = _parseIntList(result['nestingDepth'] as String); - final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 5; - final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 2; + final modeName = result['mode'] as String; + final mode = UniversalBindingMode.values.firstWhere( + (m) => m.toString().split('.').last == modeName, + orElse: () => UniversalBindingMode.singletonStrategy, + ); + final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 2; + final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 1; + final format = result['format'] as String; + final di = CherrypickDIAdapter(); final results = >[]; + void addResult( String name, - int? chainCount, - int? nestingDepth, + int chainCount, + int nestingDepth, List timings, int? memoryDiffKb, int? deltaPeakKb, int? peakRssKb, ) { timings.sort(); - var mean = timings.reduce((a, b) => a + b) / timings.length; var median = timings[timings.length ~/ 2]; var minVal = timings.first; @@ -78,8 +72,8 @@ Future main(List args) async { Future runAndCollect( String name, Future Function() fn, { - int? chainCount, - int? nestingDepth, + required int chainCount, + required int nestingDepth, }) async { for (int i = 0; i < warmups; i++) { await fn(); @@ -98,44 +92,18 @@ Future main(List args) async { addResult(name, chainCount, nestingDepth, timings, memDiffKB, deltaPeakKb, (peakRss/1024).round()); } - if (benchmark == 'all' || benchmark == 'register') { - await runAndCollect('RegisterAndResolve', () async { - return _captureReport(RegisterAndResolveBenchmark(di).report); - }); - } - if (benchmark == 'all' || benchmark == 'chain_singleton') { - for (final c in chainCounts) { - for (final d in nestDepths) { - await runAndCollect('ChainSingleton', () async { - return _captureReport(() => ChainSingletonBenchmark(di,chainCount: c, nestingDepth: d).report()); - }, chainCount: c, nestingDepth: d); - } + for (final c in chainCounts) { + for (final d in nestDepths) { + await runAndCollect('UniversalChain_$mode', () async { + return _captureReport(() => UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ).report()); + }, chainCount: c, nestingDepth: d); } } - if (benchmark == 'all' || benchmark == 'chain_factory') { - for (final c in chainCounts) { - for (final d in nestDepths) { - await runAndCollect('ChainFactory', () async { - return _captureReport(() => ChainFactoryBenchmark(di, chainCount: c, nestingDepth: d).report()); - }, chainCount: c, nestingDepth: d); - } - } - } - if (benchmark == 'all' || benchmark == 'named') { - await runAndCollect('NamedResolve', () async { - return _captureReport(NamedResolveBenchmark(di).report); - }); - } - if (benchmark == 'all' || benchmark == 'override') { - await runAndCollect('ScopeOverride', () async { - return _captureReport(ScopeOverrideBenchmark(di).report); - }); - } - if (benchmark == 'all' || benchmark == 'async_chain') { - await runAndCollect('AsyncChain', () async { - return _captureReportAsync(AsyncChainBenchmark(di).report); - }); - } if (format == 'json') { print(_toJson(results)); @@ -155,12 +123,6 @@ Future _captureReport(void Function() fn) async { sw.stop(); return sw.elapsedMicroseconds; } -Future _captureReportAsync(Future Function() fn) async { - final sw = Stopwatch()..start(); - await fn(); - sw.stop(); - return sw.elapsedMicroseconds; -} String _toPretty(List> rows) { final keys = [ diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart new file mode 100644 index 0000000..da166cf --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart @@ -0,0 +1,40 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; + +class UniversalChainBenchmark extends BenchmarkBase { + final DIAdapter di; + final int chainCount; + final int nestingDepth; + final UniversalBindingMode mode; + + UniversalChainBenchmark( + this.di, { + this.chainCount = 1, + this.nestingDepth = 3, + this.mode = UniversalBindingMode.singletonStrategy, + }) : super( + 'UniversalChain: $mode. C/D = $chainCount/$nestingDepth', + ); + + @override + void setup() { + di.setupModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + ), + ]); + } + + @override + void teardown() => di.teardown(); + + @override + void run() { + final serviceName = '${chainCount}_$nestingDepth'; + di.resolve(named: serviceName); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart new file mode 100644 index 0000000..3c9d6b1 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart @@ -0,0 +1,60 @@ +import 'package:cherrypick/cherrypick.dart'; +import 'universal_service.dart'; + +enum UniversalBindingMode { + singletonStrategy, + factoryStrategy, + asyncStrategy, +} + +class UniversalChainModule extends Module { + final int chainCount; + final int nestingDepth; + final UniversalBindingMode bindingMode; + + UniversalChainModule({ + required this.chainCount, + required this.nestingDepth, + this.bindingMode = UniversalBindingMode.singletonStrategy, + }); + + @override + void builder(Scope currentScope) { + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + switch (bindingMode) { + case UniversalBindingMode.singletonStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + case UniversalBindingMode.factoryStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName); + break; + case UniversalBindingMode.asyncStrategy: + bind() + .toProvideAsync(() async => UniversalServiceImpl( + value: depName, + dependency: await currentScope.resolveAsync(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + } + } + } + } +} diff --git a/benchmark_cherrypick/lib/scenarios/universal_service.dart b/benchmark_cherrypick/lib/scenarios/universal_service.dart new file mode 100644 index 0000000..f6ff736 --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/universal_service.dart @@ -0,0 +1,10 @@ + +abstract class UniversalService { + final String value; + final UniversalService? dependency; + UniversalService({required this.value, this.dependency}); +} + +class UniversalServiceImpl extends UniversalService { + UniversalServiceImpl({required super.value, super.dependency}); +} \ No newline at end of file From 4d412661352b423bb2cf332188bab1e232a62920 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 18:26:05 +0300 Subject: [PATCH 09/32] refactor(benchmark): clean up UniversalChainBenchmark, remove async logic, keep only sync scenario logic --- benchmark_cherrypick/bin/main.dart | 252 ++++++++++++------ .../universal_chain_async_benchmark.dart | 41 +++ .../benchmarks/universal_chain_benchmark.dart | 76 ++++-- .../lib/scenarios/universal_chain_module.dart | 119 ++++++--- 4 files changed, 360 insertions(+), 128 deletions(-) create mode 100644 benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 2d045d9..3aa79fb 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -1,15 +1,59 @@ import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:args/args.dart'; import 'dart:io'; import 'dart:math'; +enum UniversalBenchmark { + registerSingleton, + chainSingleton, + chainFactory, + chainAsync, + named, + override, +} + +UniversalScenario _toScenario(UniversalBenchmark b) { + switch (b) { + case UniversalBenchmark.registerSingleton: + return UniversalScenario.register; + case UniversalBenchmark.chainSingleton: + return UniversalScenario.chain; + case UniversalBenchmark.chainFactory: + return UniversalScenario.chain; + case UniversalBenchmark.chainAsync: + return UniversalScenario.asyncChain; + case UniversalBenchmark.named: + return UniversalScenario.named; + case UniversalBenchmark.override: + return UniversalScenario.override; + } +} + +UniversalBindingMode _toMode(UniversalBenchmark b) { + switch (b) { + case UniversalBenchmark.registerSingleton: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.chainSingleton: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.chainFactory: + return UniversalBindingMode.factoryStrategy; + case UniversalBenchmark.chainAsync: + return UniversalBindingMode.asyncStrategy; + case UniversalBenchmark.named: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.override: + return UniversalBindingMode.singletonStrategy; + } +} + Future main(List args) async { final parser = ArgParser() + ..addOption('benchmark', abbr: 'b', help: 'One of: registerSingleton, chainSingleton, chainFactory, chainAsync, named, override, all', defaultsTo: 'chainSingleton') ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts', defaultsTo: '10') ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths', defaultsTo: '5') - ..addOption('mode', abbr: 'm', help: 'Mode (singletonStrategy,factoryStrategy,asyncStrategy)', defaultsTo: 'singletonStrategy') ..addOption('repeat', abbr: 'r', help: 'Repeats for each run (>=2)', defaultsTo: '2') ..addOption('warmup', abbr: 'w', help: 'Warmup runs', defaultsTo: '1') ..addOption('format', abbr: 'f', help: 'Output format (pretty, csv, json)', defaultsTo: 'pretty') @@ -20,88 +64,151 @@ Future main(List args) async { if (result['help'] == true) { print('UniversalChainBenchmark'); print(parser.usage); + print('Example:'); + print(' dart run bin/main.dart --benchmark=chainFactory --chainCount=10 --nestingDepth=5 --format=csv'); return; } + final benchName = result['benchmark'] as String; + final isAll = benchName == 'all'; + final List allBenches = [ + UniversalBenchmark.registerSingleton, + UniversalBenchmark.chainSingleton, + UniversalBenchmark.chainFactory, + UniversalBenchmark.chainAsync, + UniversalBenchmark.named, + UniversalBenchmark.override, + ]; + + final List benchesToRun = isAll + ? allBenches + : [ + UniversalBenchmark.values.firstWhere( + (b) => b.toString().split('.').last == benchName, + orElse: () => UniversalBenchmark.chainSingleton, + ), + ]; + final chainCounts = _parseIntList(result['chainCount'] as String); final nestDepths = _parseIntList(result['nestingDepth'] as String); - final modeName = result['mode'] as String; - final mode = UniversalBindingMode.values.firstWhere( - (m) => m.toString().split('.').last == modeName, - orElse: () => UniversalBindingMode.singletonStrategy, - ); final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 2; final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 1; final format = result['format'] as String; - final di = CherrypickDIAdapter(); final results = >[]; - void addResult( - String name, - int chainCount, - int nestingDepth, - List timings, - int? memoryDiffKb, - int? deltaPeakKb, - int? peakRssKb, - ) { - 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 = sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length); - results.add({ - 'benchmark': name, - 'chainCount': chainCount, - 'nestingDepth': nestingDepth, - 'mean_us': mean.round(), - 'median_us': median.round(), - 'stddev_us': stddev.round(), - 'min_us': minVal.round(), - 'max_us': maxVal.round(), - 'trials': timings.length, - 'timings_us': timings.map((t) => t.round()).toList(), - 'memory_diff_kb': memoryDiffKb, - 'delta_peak_kb': deltaPeakKb, - 'peak_rss_kb': peakRssKb, - }); - } - - Future runAndCollect( - String name, - Future Function() fn, { - required int chainCount, - required int nestingDepth, - }) async { - for (int i = 0; i < warmups; i++) { - await fn(); - } - final timings = []; - final rssValues = []; - final memBefore = ProcessInfo.currentRss; - for (int i = 0; i < repeats; i++) { - timings.add(await fn()); - rssValues.add(ProcessInfo.currentRss); - } - final memAfter = ProcessInfo.currentRss; - final memDiffKB = ((memAfter - memBefore) / 1024).round(); - final peakRss = [...rssValues, memBefore].reduce(max); - final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); - addResult(name, chainCount, nestingDepth, timings, memDiffKB, deltaPeakKb, (peakRss/1024).round()); - } - - for (final c in chainCounts) { - for (final d in nestDepths) { - await runAndCollect('UniversalChain_$mode', () async { - return _captureReport(() => UniversalChainBenchmark( + for (final bench in benchesToRun) { + final scenario = _toScenario(bench); + final mode = _toMode(bench); + for (final c in chainCounts) { + for (final d in nestDepths) { + // --- asyncChain special case --- + if (scenario == UniversalScenario.asyncChain) { + final di = CherrypickDIAdapter(); + final benchAsync = UniversalChainAsyncBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + ); + final timings = []; + final rssValues = []; + // Warmup + for (int i = 0; i < warmups; i++) { + await benchAsync.setup(); + await benchAsync.run(); + await benchAsync.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + await benchAsync.setup(); + final sw = Stopwatch()..start(); + await benchAsync.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + await benchAsync.teardown(); + } + final memAfter = ProcessInfo.currentRss; + final memDiffKB = ((memAfter - memBefore) / 1024).round(); + final peakRss = [...rssValues, memBefore].reduce(max); + final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + 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 = 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.round(), + 'median_us': median.round(), + 'stddev_us': stddev.round(), + 'min_us': minVal.round(), + 'max_us': maxVal.round(), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.round()).toList(), + 'memory_diff_kb': memDiffKB, + 'delta_peak_kb': deltaPeakKb, + 'peak_rss_kb': (peakRss / 1024).round(), + }); + continue; + } + // --- Sync-case --- + final di = CherrypickDIAdapter(); + final benchSync = UniversalChainBenchmark( di, chainCount: c, nestingDepth: d, mode: mode, - ).report()); - }, chainCount: c, nestingDepth: d); + scenario: scenario, + ); + final timings = []; + final rssValues = []; + // Warmup + for (int i = 0; i < warmups; i++) { + benchSync.setup(); + benchSync.run(); + benchSync.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + benchSync.setup(); + final sw = Stopwatch()..start(); + benchSync.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + benchSync.teardown(); + } + final memAfter = ProcessInfo.currentRss; + final memDiffKB = ((memAfter - memBefore) / 1024).round(); + final peakRss = [...rssValues, memBefore].reduce(max); + final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + 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 = 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.round(), + 'median_us': median.round(), + 'stddev_us': stddev.round(), + 'min_us': minVal.round(), + 'max_us': maxVal.round(), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.round()).toList(), + 'memory_diff_kb': memDiffKB, + 'delta_peak_kb': deltaPeakKb, + 'peak_rss_kb': (peakRss / 1024).round(), + }); + } } } @@ -117,13 +224,6 @@ Future main(List args) async { // --- helpers --- List _parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); -Future _captureReport(void Function() fn) async { - final sw = Stopwatch()..start(); - fn(); - sw.stop(); - return sw.elapsedMicroseconds; -} - String _toPretty(List> rows) { final keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', @@ -133,7 +233,6 @@ String _toPretty(List> rows) { final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); return ([header] + lines).join('\n'); } - String _toCsv(List> rows) { final keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', @@ -149,7 +248,6 @@ String _toCsv(List> rows) { ).toList(); return ([header] + lines).join('\n'); } - String _toJson(List> rows) { return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; } diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart new file mode 100644 index 0000000..524dadb --- /dev/null +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart @@ -0,0 +1,41 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; + +class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { + final DIAdapter di; + final int chainCount; + final int nestingDepth; + final UniversalBindingMode mode; + + UniversalChainAsyncBenchmark( + this.di, { + this.chainCount = 1, + this.nestingDepth = 3, + this.mode = UniversalBindingMode.asyncStrategy, + }) : super('UniversalAsync: asyncChain/$mode CD=$chainCount/$nestingDepth'); + + @override + Future setup() async { + di.setupModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: UniversalScenario.asyncChain, + ) + ]); + } + + @override + Future teardown() async { + di.teardown(); + } + + @override + Future run() async { + final serviceName = '${chainCount}_$nestingDepth'; + await di.resolveAsync(named: serviceName); + } +} diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart index da166cf..0818608 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart @@ -4,37 +4,77 @@ import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; class UniversalChainBenchmark extends BenchmarkBase { - final DIAdapter di; + final DIAdapter _di; final int chainCount; final int nestingDepth; final UniversalBindingMode mode; + final UniversalScenario scenario; + DIAdapter? _childDi; UniversalChainBenchmark( - this.di, { - this.chainCount = 1, - this.nestingDepth = 3, - this.mode = UniversalBindingMode.singletonStrategy, - }) : super( - 'UniversalChain: $mode. C/D = $chainCount/$nestingDepth', - ); + this._di, { + this.chainCount = 1, + this.nestingDepth = 3, + this.mode = UniversalBindingMode.singletonStrategy, + this.scenario = UniversalScenario.chain, + }) : super('Universal: $scenario/$mode CD=$chainCount/$nestingDepth'); @override void setup() { - di.setupModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: mode, - ), - ]); + switch (scenario) { + case UniversalScenario.override: + _di.setupModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.register, + ) + ]); + _childDi = _di.openSubScope('child'); + _childDi!.setupModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.register, + ) + ]); + break; + default: + _di.setupModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: scenario, + ) + ]); + break; + } } @override - void teardown() => di.teardown(); + void teardown() => _di.teardown(); @override void run() { - final serviceName = '${chainCount}_$nestingDepth'; - di.resolve(named: serviceName); + switch (scenario) { + case UniversalScenario.register: + _di.resolve(); + break; + case UniversalScenario.named: + _di.resolve(named: 'impl2'); + break; + case UniversalScenario.chain: + final serviceName = '${chainCount}_$nestingDepth'; + _di.resolve(named: serviceName); + break; + case UniversalScenario.override: + _childDi!.resolve(); + break; + case UniversalScenario.asyncChain: + throw UnsupportedError('asyncChain supported only in UniversalChainAsyncBenchmark'); + } } } diff --git a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart index 3c9d6b1..dd89644 100644 --- a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart +++ b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart @@ -7,54 +7,107 @@ enum UniversalBindingMode { asyncStrategy, } +enum UniversalScenario { + register, + chain, + named, + override, + asyncChain, +} + class UniversalChainModule extends Module { final int chainCount; final int nestingDepth; final UniversalBindingMode bindingMode; + final UniversalScenario scenario; UniversalChainModule({ required this.chainCount, required this.nestingDepth, this.bindingMode = UniversalBindingMode.singletonStrategy, + this.scenario = UniversalScenario.chain, }); @override void builder(Scope currentScope) { - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - switch (bindingMode) { - case UniversalBindingMode.singletonStrategy: - bind() - .toProvide(() => UniversalServiceImpl( - value: depName, - dependency: currentScope.tryResolve(named: prevDepName), - )) - .withName(depName) - .singleton(); - break; - case UniversalBindingMode.factoryStrategy: - bind() - .toProvide(() => UniversalServiceImpl( - value: depName, - dependency: currentScope.tryResolve(named: prevDepName), - )) - .withName(depName); - break; - case UniversalBindingMode.asyncStrategy: - bind() - .toProvideAsync(() async => UniversalServiceImpl( - value: depName, - dependency: await currentScope.resolveAsync(named: prevDepName), - )) - .withName(depName) - .singleton(); - break; + if (scenario == UniversalScenario.asyncChain) { + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + bind() + .toProvideAsync(() async { + final prev = level > 1 + ? await currentScope.resolveAsync(named: prevDepName) + : null; + return UniversalServiceImpl( + value: depName, + dependency: prev, + ); + }) + .withName(depName) + .singleton(); } } + return; + } + + switch (scenario) { + case UniversalScenario.register: + bind() + .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) + .singleton(); + break; + case UniversalScenario.named: + bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); + bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); + break; + case UniversalScenario.chain: + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + switch (bindingMode) { + case UniversalBindingMode.singletonStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + case UniversalBindingMode.factoryStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName); + break; + case UniversalBindingMode.asyncStrategy: + bind() + .toProvideAsync(() async => UniversalServiceImpl( + value: depName, + dependency: await currentScope.resolveAsync(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + } + } + } + break; + case UniversalScenario.override: + // handled at benchmark level + break; + case UniversalScenario.asyncChain: + // already handled above + break; } } } From 0fc190717378093d066b9d0267de40c5681194dc Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 18:36:11 +0300 Subject: [PATCH 10/32] chore(cleanup): remove unused legacy benchmarks and scenario files --- .../lib/benchmarks/async_chain_benchmark.dart | 23 ---------- .../benchmarks/chain_factory_benchmark.dart | 38 ----------------- .../benchmarks/chain_singleton_benchmark.dart | 38 ----------------- .../benchmarks/named_resolve_benchmark.dart | 22 ---------- .../register_and_resolve_benchmark.dart | 23 ---------- .../benchmarks/scope_override_benchmark.dart | 29 ------------- .../lib/scenarios/app_module.dart | 9 ---- .../lib/scenarios/async_chain_module.dart | 20 --------- .../lib/scenarios/chain_factory_module.dart | 42 ------------------- .../lib/scenarios/chain_singleton_module.dart | 42 ------------------- .../lib/scenarios/child_impl.dart | 2 - .../lib/scenarios/child_override_module.dart | 10 ----- .../lib/scenarios/foo_service.dart | 1 - .../lib/scenarios/named_module.dart | 12 ------ .../lib/scenarios/parent_impl.dart | 2 - .../lib/scenarios/parent_module.dart | 10 ----- .../lib/scenarios/service.dart | 5 --- .../lib/scenarios/service_impl.dart | 5 --- .../lib/scenarios/shared.dart | 1 - .../lib/utils/benchmark_utils.dart | 29 ------------- 20 files changed, 363 deletions(-) delete mode 100644 benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/app_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/async_chain_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/chain_factory_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/child_impl.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/child_override_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/foo_service.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/named_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/parent_impl.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/parent_module.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/service.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/service_impl.dart delete mode 100644 benchmark_cherrypick/lib/scenarios/shared.dart delete mode 100644 benchmark_cherrypick/lib/utils/benchmark_utils.dart diff --git a/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart deleted file mode 100644 index 68b8ef9..0000000 --- a/benchmark_cherrypick/lib/benchmarks/async_chain_benchmark.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/async_chain_module.dart'; - -class AsyncChainBenchmark extends AsyncBenchmarkBase { - final DIAdapter di; - AsyncChainBenchmark(this.di) : super('AsyncChain (A->B->C, async)'); - - @override - Future setup() async { - di.setupModules([AsyncChainModule()]); - } - - @override - Future teardown() async { - di.teardown(); - } - - @override - Future run() async { - await di.resolveAsync(); - } -} diff --git a/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart deleted file mode 100644 index ddb2b0d..0000000 --- a/benchmark_cherrypick/lib/benchmarks/chain_factory_benchmark.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/chain_factory_module.dart'; -import 'package:benchmark_cherrypick/scenarios/service.dart'; - -class ChainFactoryBenchmark extends BenchmarkBase { - final DIAdapter di; - final int chainCount; - final int nestingDepth; - - ChainFactoryBenchmark( - this.di, { - this.chainCount = 1, - this.nestingDepth = 3, - }) : super( - 'ChainFactory (A->B->C, factory). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - - @override - void setup() { - di.setupModules([ - ChainFactoryModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - ), - ]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - di.resolve(named: serviceName); - } -} diff --git a/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart deleted file mode 100644 index a1ba257..0000000 --- a/benchmark_cherrypick/lib/benchmarks/chain_singleton_benchmark.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_harness/benchmark_harness.dart'; -import '../scenarios/chain_singleton_module.dart'; -import '../scenarios/service.dart'; - -class ChainSingletonBenchmark extends BenchmarkBase { - final DIAdapter di; - final int chainCount; - final int nestingDepth; - - ChainSingletonBenchmark( - this.di, { - this.chainCount = 1, - this.nestingDepth = 3, - }) : super( - 'ChainSingleton (A->B->C, singleton). ' - 'C/D = $chainCount/$nestingDepth. ', - ); - - @override - void setup() { - di.setupModules([ - ChainSingletonModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - ), - ]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - final serviceName = '${chainCount.toString()}_${nestingDepth.toString()}'; - di.resolve(named: serviceName); - } -} diff --git a/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart deleted file mode 100644 index 7a7b8a7..0000000 --- a/benchmark_cherrypick/lib/benchmarks/named_resolve_benchmark.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/named_module.dart'; - -class NamedResolveBenchmark extends BenchmarkBase { - final DIAdapter di; - - NamedResolveBenchmark(this.di) : super('NamedResolve (by name)'); - - @override - void setup() { - di.setupModules([NamedModule()]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - di.resolve(named: 'impl2'); - } -} diff --git a/benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart deleted file mode 100644 index a2442fe..0000000 --- a/benchmark_cherrypick/lib/benchmarks/register_and_resolve_benchmark.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/app_module.dart'; -import 'package:benchmark_cherrypick/scenarios/foo_service.dart'; - -class RegisterAndResolveBenchmark extends BenchmarkBase { - final DIAdapter di; - - RegisterAndResolveBenchmark(this.di) : super('RegisterAndResolve'); - - @override - void setup() { - di.setupModules([AppModule()]); - } - - @override - void run() { - di.resolve(); - } - - @override - void teardown() => di.teardown(); -} diff --git a/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart deleted file mode 100644 index 4c96a3b..0000000 --- a/benchmark_cherrypick/lib/benchmarks/scope_override_benchmark.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/parent_module.dart'; -import 'package:benchmark_cherrypick/scenarios/child_override_module.dart'; -import 'package:benchmark_cherrypick/scenarios/shared.dart'; -import 'package:benchmark_cherrypick/scenarios/child_impl.dart'; - -class ScopeOverrideBenchmark extends BenchmarkBase { - final DIAdapter di; - late DIAdapter childDi; - - ScopeOverrideBenchmark(this.di) : super('ScopeOverride (child overrides parent)'); - - @override - void setup() { - di.setupModules([ParentModule()]); - childDi = di.openSubScope('child'); - childDi.setupModules([ChildOverrideModule()]); - } - - @override - void teardown() => di.teardown(); - - @override - void run() { - final resolved = childDi.resolve(); - assert(resolved is ChildImpl); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/app_module.dart b/benchmark_cherrypick/lib/scenarios/app_module.dart deleted file mode 100644 index fa68fd9..0000000 --- a/benchmark_cherrypick/lib/scenarios/app_module.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; -import 'foo_service.dart'; - -class AppModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => FooService()); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/async_chain_module.dart b/benchmark_cherrypick/lib/scenarios/async_chain_module.dart deleted file mode 100644 index e0044e9..0000000 --- a/benchmark_cherrypick/lib/scenarios/async_chain_module.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; - -class AsyncA {} -class AsyncB { - final AsyncA a; - AsyncB(this.a); -} -class AsyncC { - final AsyncB b; - AsyncC(this.b); -} - -class AsyncChainModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvideAsync(() async => AsyncA()).singleton(); - bind().toProvideAsync(() async => AsyncB(await currentScope.resolveAsync())).singleton(); - bind().toProvideAsync(() async => AsyncC(await currentScope.resolveAsync())).singleton(); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart b/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart deleted file mode 100644 index ade4ccf..0000000 --- a/benchmark_cherrypick/lib/scenarios/chain_factory_module.dart +++ /dev/null @@ -1,42 +0,0 @@ -// === DI graph: A -> B -> C (factory/no singleton) === -import 'package:cherrypick/cherrypick.dart'; - -import 'service.dart'; -import 'service_impl.dart'; - -class ChainFactoryModule extends Module { - // количество независимых цепочек - final int chainCount; - - // глубина вложенности - final int nestingDepth; - - ChainFactoryModule({ - required this.chainCount, - required this.nestingDepth, - }); - - @override - void builder(Scope currentScope) { - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - - final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; - final depName = '${chain.toString()}_${level.toString()}'; - - bind() - .toProvide( - () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) - .withName(depName); - } - } - } -} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart b/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart deleted file mode 100644 index 72fa5ca..0000000 --- a/benchmark_cherrypick/lib/scenarios/chain_singleton_module.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; - -import 'service.dart'; -import 'service_impl.dart'; - -class ChainSingletonModule extends Module { - // количество независимых цепочек - final int chainCount; - - // глубина вложенности - final int nestingDepth; - - ChainSingletonModule({ - required this.chainCount, - required this.nestingDepth, - }); - - @override - void builder(Scope currentScope) { - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - - final prevDepName = '${chain.toString()}_${(level - 1).toString()}'; - final depName = '${chain.toString()}_${level.toString()}'; - - bind() - .toProvide( - () => ServiceImpl( - value: depName, - dependency: currentScope.tryResolve( - named: prevDepName, - ), - ), - ) - .withName(depName) - .singleton(); - } - } - } -} diff --git a/benchmark_cherrypick/lib/scenarios/child_impl.dart b/benchmark_cherrypick/lib/scenarios/child_impl.dart deleted file mode 100644 index e564b60..0000000 --- a/benchmark_cherrypick/lib/scenarios/child_impl.dart +++ /dev/null @@ -1,2 +0,0 @@ -import 'shared.dart'; -class ChildImpl extends Shared {} diff --git a/benchmark_cherrypick/lib/scenarios/child_override_module.dart b/benchmark_cherrypick/lib/scenarios/child_override_module.dart deleted file mode 100644 index 3ace258..0000000 --- a/benchmark_cherrypick/lib/scenarios/child_override_module.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; -import 'child_impl.dart'; -import 'shared.dart'; - -class ChildOverrideModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => ChildImpl()).singleton(); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/foo_service.dart b/benchmark_cherrypick/lib/scenarios/foo_service.dart deleted file mode 100644 index d72c805..0000000 --- a/benchmark_cherrypick/lib/scenarios/foo_service.dart +++ /dev/null @@ -1 +0,0 @@ -class FooService {} diff --git a/benchmark_cherrypick/lib/scenarios/named_module.dart b/benchmark_cherrypick/lib/scenarios/named_module.dart deleted file mode 100644 index a0708c0..0000000 --- a/benchmark_cherrypick/lib/scenarios/named_module.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; - -class Impl1 {} -class Impl2 {} - -class NamedModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => Impl1()).withName('impl1'); - bind().toProvide(() => Impl2()).withName('impl2'); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/parent_impl.dart b/benchmark_cherrypick/lib/scenarios/parent_impl.dart deleted file mode 100644 index e6a5f40..0000000 --- a/benchmark_cherrypick/lib/scenarios/parent_impl.dart +++ /dev/null @@ -1,2 +0,0 @@ -import 'shared.dart'; -class ParentImpl extends Shared {} diff --git a/benchmark_cherrypick/lib/scenarios/parent_module.dart b/benchmark_cherrypick/lib/scenarios/parent_module.dart deleted file mode 100644 index 2e3d83f..0000000 --- a/benchmark_cherrypick/lib/scenarios/parent_module.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; -import 'parent_impl.dart'; -import 'shared.dart'; - -class ParentModule extends Module { - @override - void builder(Scope currentScope) { - bind().toProvide(() => ParentImpl()).singleton(); - } -} diff --git a/benchmark_cherrypick/lib/scenarios/service.dart b/benchmark_cherrypick/lib/scenarios/service.dart deleted file mode 100644 index 065646b..0000000 --- a/benchmark_cherrypick/lib/scenarios/service.dart +++ /dev/null @@ -1,5 +0,0 @@ -abstract class Service { - final dynamic value; - final Service? dependency; - Service({required this.value, this.dependency}); -} diff --git a/benchmark_cherrypick/lib/scenarios/service_impl.dart b/benchmark_cherrypick/lib/scenarios/service_impl.dart deleted file mode 100644 index 58ee09b..0000000 --- a/benchmark_cherrypick/lib/scenarios/service_impl.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'service.dart'; - -class ServiceImpl extends Service { - ServiceImpl({required super.value, super.dependency}); -} diff --git a/benchmark_cherrypick/lib/scenarios/shared.dart b/benchmark_cherrypick/lib/scenarios/shared.dart deleted file mode 100644 index f3aa38b..0000000 --- a/benchmark_cherrypick/lib/scenarios/shared.dart +++ /dev/null @@ -1 +0,0 @@ -class Shared {} diff --git a/benchmark_cherrypick/lib/utils/benchmark_utils.dart b/benchmark_cherrypick/lib/utils/benchmark_utils.dart deleted file mode 100644 index 4d1a302..0000000 --- a/benchmark_cherrypick/lib/utils/benchmark_utils.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; - -/// Миксин для упрощения работы с CherryPick Scope в бенчмарках. -mixin BenchmarkWithScope { - Scope? _scope; - - /// Отключить глобальные проверки циклов и создать корневой scope с модулями. - void setupScope(List modules, - {bool disableCycleDetection = true, - bool disableCrossScopeCycleDetection = true}) { - if (disableCycleDetection) { - CherryPick.disableGlobalCycleDetection(); - } - if (disableCrossScopeCycleDetection) { - CherryPick.disableGlobalCrossScopeCycleDetection(); - } - _scope = CherryPick.openRootScope(); - _scope!.installModules(modules); - } - - /// Закрывает текущий scope. - void teardownScope() { - CherryPick.closeRootScope(); - _scope = null; - } - - /// Получить текущий scope. Не null после setupScope. - Scope get scope => _scope!; -} From bae940f374765cd5f90eceb0cfe34a913506e8aa Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 21:53:13 +0300 Subject: [PATCH 11/32] refactor(main): extract BenchmarkRunner and BenchmarkResult, simplify main loop, unify sync/async cases --- benchmark_cherrypick/bin/main.dart | 250 ++++++++++++++++------------- 1 file changed, 136 insertions(+), 114 deletions(-) diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 3aa79fb..21e91bd 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -31,7 +31,6 @@ UniversalScenario _toScenario(UniversalBenchmark b) { return UniversalScenario.override; } } - UniversalBindingMode _toMode(UniversalBenchmark b) { switch (b) { case UniversalBenchmark.registerSingleton: @@ -49,18 +48,114 @@ UniversalBindingMode _toMode(UniversalBenchmark b) { } } +typedef SyncBench = UniversalChainBenchmark; +typedef AsyncBench = UniversalChainAsyncBenchmark; + +class BenchmarkResult { + final List timings; + final int memoryDiffKb; + final int deltaPeakKb; + final int peakRssKb; + BenchmarkResult({ + required this.timings, + required this.memoryDiffKb, + required this.deltaPeakKb, + required this.peakRssKb, + }); + factory BenchmarkResult.collect({ + required List timings, + required List rssValues, + required int memBefore, + }) { + final memAfter = ProcessInfo.currentRss; + final memDiffKB = ((memAfter - memBefore) / 1024).round(); + final peakRss = [...rssValues, memBefore].reduce(max); + final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + return BenchmarkResult( + timings: timings, + memoryDiffKb: memDiffKB, + deltaPeakKb: deltaPeakKb, + peakRssKb: (peakRss / 1024).round(), + ); + } +} + +class BenchmarkRunner { + static Future runSync({ + required SyncBench benchmark, + required int warmups, + required int repeats, + }) async { + final timings = []; + final rssValues = []; + for (int i = 0; i < warmups; i++) { + benchmark.setup(); + benchmark.run(); + benchmark.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + benchmark.setup(); + final sw = Stopwatch()..start(); + benchmark.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + benchmark.teardown(); + } + return BenchmarkResult.collect( + timings: timings, + rssValues: rssValues, + memBefore: memBefore, + ); + } + static Future runAsync({ + required AsyncBench benchmark, + required int warmups, + required int repeats, + }) async { + final timings = []; + final rssValues = []; + for (int i = 0; i < warmups; i++) { + await benchmark.setup(); + await benchmark.run(); + await benchmark.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + await benchmark.setup(); + final sw = Stopwatch()..start(); + await benchmark.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + await benchmark.teardown(); + } + return BenchmarkResult.collect( + timings: timings, + rssValues: rssValues, + memBefore: memBefore, + ); + } +} + +T parseEnum(String value, List values, T defaultValue) { + return values.firstWhere( + (v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); +} + Future main(List args) async { final parser = ArgParser() - ..addOption('benchmark', abbr: 'b', help: 'One of: registerSingleton, chainSingleton, chainFactory, chainAsync, named, override, all', defaultsTo: 'chainSingleton') - ..addOption('chainCount', abbr: 'c', help: 'Comma-separated chainCounts', defaultsTo: '10') - ..addOption('nestingDepth', abbr: 'd', help: 'Comma-separated depths', defaultsTo: '5') - ..addOption('repeat', abbr: 'r', help: 'Repeats for each run (>=2)', defaultsTo: '2') - ..addOption('warmup', abbr: 'w', help: 'Warmup runs', defaultsTo: '1') - ..addOption('format', abbr: 'f', help: 'Output format (pretty, csv, json)', defaultsTo: 'pretty') + ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') + ..addOption('chainCount', abbr: 'c', defaultsTo: '10') + ..addOption('nestingDepth', abbr: 'd', defaultsTo: '5') + ..addOption('repeat', abbr: 'r', defaultsTo: '2') + ..addOption('warmup', abbr: 'w', defaultsTo: '1') + ..addOption('format', abbr: 'f', defaultsTo: 'pretty') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); - final result = parser.parse(args); - if (result['help'] == true) { print('UniversalChainBenchmark'); print(parser.usage); @@ -68,41 +163,25 @@ Future main(List args) async { print(' dart run bin/main.dart --benchmark=chainFactory --chainCount=10 --nestingDepth=5 --format=csv'); return; } - - final benchName = result['benchmark'] as String; + final benchName = result['benchmark'] as String; final isAll = benchName == 'all'; - final List allBenches = [ - UniversalBenchmark.registerSingleton, - UniversalBenchmark.chainSingleton, - UniversalBenchmark.chainFactory, - UniversalBenchmark.chainAsync, - UniversalBenchmark.named, - UniversalBenchmark.override, - ]; - + final List allBenches = UniversalBenchmark.values; final List benchesToRun = isAll ? allBenches - : [ - UniversalBenchmark.values.firstWhere( - (b) => b.toString().split('.').last == benchName, - orElse: () => UniversalBenchmark.chainSingleton, - ), - ]; - + : [parseEnum(benchName, UniversalBenchmark.values, UniversalBenchmark.chainSingleton)]; final chainCounts = _parseIntList(result['chainCount'] as String); - final nestDepths = _parseIntList(result['nestingDepth'] as String); - final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 2; - final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 1; - final format = result['format'] as String; + final nestDepths = _parseIntList(result['nestingDepth'] as String); + final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 2; + final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 1; + final format = result['format'] as String; final results = >[]; - for (final bench in benchesToRun) { final scenario = _toScenario(bench); - final mode = _toMode(bench); + final mode = _toMode(bench); for (final c in chainCounts) { for (final d in nestDepths) { - // --- asyncChain special case --- + BenchmarkResult benchResult; if (scenario == UniversalScenario.asyncChain) { final di = CherrypickDIAdapter(); final benchAsync = UniversalChainAsyncBenchmark( @@ -111,82 +190,27 @@ Future main(List args) async { nestingDepth: d, mode: mode, ); - final timings = []; - final rssValues = []; - // Warmup - for (int i = 0; i < warmups; i++) { - await benchAsync.setup(); - await benchAsync.run(); - await benchAsync.teardown(); - } - final memBefore = ProcessInfo.currentRss; - for (int i = 0; i < repeats; i++) { - await benchAsync.setup(); - final sw = Stopwatch()..start(); - await benchAsync.run(); - sw.stop(); - timings.add(sw.elapsedMicroseconds); - rssValues.add(ProcessInfo.currentRss); - await benchAsync.teardown(); - } - final memAfter = ProcessInfo.currentRss; - final memDiffKB = ((memAfter - memBefore) / 1024).round(); - final peakRss = [...rssValues, memBefore].reduce(max); - final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); - 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 = 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.round(), - 'median_us': median.round(), - 'stddev_us': stddev.round(), - 'min_us': minVal.round(), - 'max_us': maxVal.round(), - 'trials': timings.length, - 'timings_us': timings.map((t) => t.round()).toList(), - 'memory_diff_kb': memDiffKB, - 'delta_peak_kb': deltaPeakKb, - 'peak_rss_kb': (peakRss / 1024).round(), - }); - continue; + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: warmups, + repeats: repeats, + ); + } else { + final di = CherrypickDIAdapter(); + final benchSync = UniversalChainBenchmark( + di, + chainCount: c, + nestingDepth: d, + mode: mode, + scenario: scenario, + ); + benchResult = await BenchmarkRunner.runSync( + benchmark: benchSync, + warmups: warmups, + repeats: repeats, + ); } - // --- Sync-case --- - final di = CherrypickDIAdapter(); - final benchSync = UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - final timings = []; - final rssValues = []; - // Warmup - for (int i = 0; i < warmups; i++) { - benchSync.setup(); - benchSync.run(); - benchSync.teardown(); - } - final memBefore = ProcessInfo.currentRss; - for (int i = 0; i < repeats; i++) { - benchSync.setup(); - final sw = Stopwatch()..start(); - benchSync.run(); - sw.stop(); - timings.add(sw.elapsedMicroseconds); - rssValues.add(ProcessInfo.currentRss); - benchSync.teardown(); - } - final memAfter = ProcessInfo.currentRss; - final memDiffKB = ((memAfter - memBefore) / 1024).round(); - final peakRss = [...rssValues, memBefore].reduce(max); - final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + final timings = benchResult.timings; timings.sort(); var mean = timings.reduce((a, b) => a + b) / timings.length; var median = timings[timings.length ~/ 2]; @@ -204,14 +228,13 @@ Future main(List args) async { 'max_us': maxVal.round(), 'trials': timings.length, 'timings_us': timings.map((t) => t.round()).toList(), - 'memory_diff_kb': memDiffKB, - 'delta_peak_kb': deltaPeakKb, - 'peak_rss_kb': (peakRss / 1024).round(), + 'memory_diff_kb': benchResult.memoryDiffKb, + 'delta_peak_kb': benchResult.deltaPeakKb, + 'peak_rss_kb': benchResult.peakRssKb, }); } } } - if (format == 'json') { print(_toJson(results)); } else if (format == 'csv') { @@ -223,7 +246,6 @@ Future main(List args) async { // --- helpers --- List _parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); - String _toPretty(List> rows) { final keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', From 3ce21f55e44d971603a719fc585145d1db06b463 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:02:41 +0300 Subject: [PATCH 12/32] refactor(report): extract ReportGenerator abstraction for pretty/csv/json; simplify report rendering in main --- benchmark_cherrypick/bin/main.dart | 89 ++++++++++++++++++------------ 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 21e91bd..586e1df 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -146,6 +146,52 @@ T parseEnum(String value, List values, T defaultValue) { ); } +/// --- Report Generation --- +abstract class ReportGenerator { + String render(List> results); + List get keys; +} +class PrettyReport extends ReportGenerator { + @override + final List keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; + @override + String render(List> rows) { + final header = keys.join('\t'); + final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); + return ([header] + lines).join('\n'); + } +} +class CsvReport extends ReportGenerator { + @override + final List keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; + @override + String render(List> rows) { + final header = keys.join(','); + final lines = rows.map((r) => + keys.map((k) { + final v = r[k]; + if (v is List) return '"${v.join(';')}"'; + return (v ?? '').toString(); + }).join(',') + ).toList(); + return ([header] + lines).join('\n'); + } +} +class JsonReport extends ReportGenerator { + @override + List get keys => []; + @override + String render(List> rows) { + return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; + } +} + Future main(List args) async { final parser = ArgParser() ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') @@ -235,42 +281,13 @@ Future main(List args) async { } } } - if (format == 'json') { - print(_toJson(results)); - } else if (format == 'csv') { - print(_toCsv(results)); - } else { - print(_toPretty(results)); - } + + final reportGenerators = { + 'pretty': PrettyReport(), + 'csv': CsvReport(), + 'json': JsonReport(), + }; + print(reportGenerators[format]?.render(results) ?? PrettyReport().render(results)); } -// --- helpers --- List _parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); -String _toPretty(List> rows) { - final keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' - ]; - final header = keys.join('\t'); - final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); - return ([header] + lines).join('\n'); -} -String _toCsv(List> rows) { - final keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' - ]; - final header = keys.join(','); - final lines = rows.map((r) => - keys.map((k) { - final v = r[k]; - if (v is List) return '"${v.join(';')}"'; - return (v ?? '').toString(); - }).join(',') - ).toList(); - return ([header] + lines).join('\n'); -} -String _toJson(List> rows) { - return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; -} -// --- end helpers --- From 09ed1865443b488a886a459bffe574b3851f3e73 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:19:13 +0300 Subject: [PATCH 13/32] =?UTF-8?q?refactor(cli):=20modularize=20CLI=20?= =?UTF-8?q?=E2=80=94=20extract=20parser,=20runner,=20report=20and=20main?= =?UTF-8?q?=20logic=20into=20dedicated=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmark_cherrypick/bin/main.dart | 294 +----------------- .../lib/cli/benchmark_cli.dart | 76 +++++ benchmark_cherrypick/lib/cli/parser.dart | 104 +++++++ .../lib/cli/report/csv_report.dart | 21 ++ .../lib/cli/report/json_report.dart | 10 + .../lib/cli/report/pretty_report.dart | 15 + .../lib/cli/report/report_generator.dart | 4 + benchmark_cherrypick/lib/cli/runner.dart | 85 +++++ 8 files changed, 318 insertions(+), 291 deletions(-) create mode 100644 benchmark_cherrypick/lib/cli/benchmark_cli.dart create mode 100644 benchmark_cherrypick/lib/cli/parser.dart create mode 100644 benchmark_cherrypick/lib/cli/report/csv_report.dart create mode 100644 benchmark_cherrypick/lib/cli/report/json_report.dart create mode 100644 benchmark_cherrypick/lib/cli/report/pretty_report.dart create mode 100644 benchmark_cherrypick/lib/cli/report/report_generator.dart create mode 100644 benchmark_cherrypick/lib/cli/runner.dart diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart index 586e1df..3c51a09 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_cherrypick/bin/main.dart @@ -1,293 +1,5 @@ -import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; -import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; -import 'package:args/args.dart'; -import 'dart:io'; -import 'dart:math'; - -enum UniversalBenchmark { - registerSingleton, - chainSingleton, - chainFactory, - chainAsync, - named, - override, -} - -UniversalScenario _toScenario(UniversalBenchmark b) { - switch (b) { - case UniversalBenchmark.registerSingleton: - return UniversalScenario.register; - case UniversalBenchmark.chainSingleton: - return UniversalScenario.chain; - case UniversalBenchmark.chainFactory: - return UniversalScenario.chain; - case UniversalBenchmark.chainAsync: - return UniversalScenario.asyncChain; - case UniversalBenchmark.named: - return UniversalScenario.named; - case UniversalBenchmark.override: - return UniversalScenario.override; - } -} -UniversalBindingMode _toMode(UniversalBenchmark b) { - switch (b) { - case UniversalBenchmark.registerSingleton: - return UniversalBindingMode.singletonStrategy; - case UniversalBenchmark.chainSingleton: - return UniversalBindingMode.singletonStrategy; - case UniversalBenchmark.chainFactory: - return UniversalBindingMode.factoryStrategy; - case UniversalBenchmark.chainAsync: - return UniversalBindingMode.asyncStrategy; - case UniversalBenchmark.named: - return UniversalBindingMode.singletonStrategy; - case UniversalBenchmark.override: - return UniversalBindingMode.singletonStrategy; - } -} - -typedef SyncBench = UniversalChainBenchmark; -typedef AsyncBench = UniversalChainAsyncBenchmark; - -class BenchmarkResult { - final List timings; - final int memoryDiffKb; - final int deltaPeakKb; - final int peakRssKb; - BenchmarkResult({ - required this.timings, - required this.memoryDiffKb, - required this.deltaPeakKb, - required this.peakRssKb, - }); - factory BenchmarkResult.collect({ - required List timings, - required List rssValues, - required int memBefore, - }) { - final memAfter = ProcessInfo.currentRss; - final memDiffKB = ((memAfter - memBefore) / 1024).round(); - final peakRss = [...rssValues, memBefore].reduce(max); - final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); - return BenchmarkResult( - timings: timings, - memoryDiffKb: memDiffKB, - deltaPeakKb: deltaPeakKb, - peakRssKb: (peakRss / 1024).round(), - ); - } -} - -class BenchmarkRunner { - static Future runSync({ - required SyncBench benchmark, - required int warmups, - required int repeats, - }) async { - final timings = []; - final rssValues = []; - for (int i = 0; i < warmups; i++) { - benchmark.setup(); - benchmark.run(); - benchmark.teardown(); - } - final memBefore = ProcessInfo.currentRss; - for (int i = 0; i < repeats; i++) { - benchmark.setup(); - final sw = Stopwatch()..start(); - benchmark.run(); - sw.stop(); - timings.add(sw.elapsedMicroseconds); - rssValues.add(ProcessInfo.currentRss); - benchmark.teardown(); - } - return BenchmarkResult.collect( - timings: timings, - rssValues: rssValues, - memBefore: memBefore, - ); - } - static Future runAsync({ - required AsyncBench benchmark, - required int warmups, - required int repeats, - }) async { - final timings = []; - final rssValues = []; - for (int i = 0; i < warmups; i++) { - await benchmark.setup(); - await benchmark.run(); - await benchmark.teardown(); - } - final memBefore = ProcessInfo.currentRss; - for (int i = 0; i < repeats; i++) { - await benchmark.setup(); - final sw = Stopwatch()..start(); - await benchmark.run(); - sw.stop(); - timings.add(sw.elapsedMicroseconds); - rssValues.add(ProcessInfo.currentRss); - await benchmark.teardown(); - } - return BenchmarkResult.collect( - timings: timings, - rssValues: rssValues, - memBefore: memBefore, - ); - } -} - -T parseEnum(String value, List values, T defaultValue) { - return values.firstWhere( - (v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(), - orElse: () => defaultValue, - ); -} - -/// --- Report Generation --- -abstract class ReportGenerator { - String render(List> results); - List get keys; -} -class PrettyReport extends ReportGenerator { - @override - final List keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' - ]; - @override - String render(List> rows) { - final header = keys.join('\t'); - final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); - return ([header] + lines).join('\n'); - } -} -class CsvReport extends ReportGenerator { - @override - final List keys = [ - 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', - 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' - ]; - @override - String render(List> rows) { - final header = keys.join(','); - final lines = rows.map((r) => - keys.map((k) { - final v = r[k]; - if (v is List) return '"${v.join(';')}"'; - return (v ?? '').toString(); - }).join(',') - ).toList(); - return ([header] + lines).join('\n'); - } -} -class JsonReport extends ReportGenerator { - @override - List get keys => []; - @override - String render(List> rows) { - return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; - } -} +import 'package:benchmark_cherrypick/cli/benchmark_cli.dart'; Future main(List args) async { - final parser = ArgParser() - ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') - ..addOption('chainCount', abbr: 'c', defaultsTo: '10') - ..addOption('nestingDepth', abbr: 'd', defaultsTo: '5') - ..addOption('repeat', abbr: 'r', defaultsTo: '2') - ..addOption('warmup', abbr: 'w', defaultsTo: '1') - ..addOption('format', abbr: 'f', defaultsTo: 'pretty') - ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); - final result = parser.parse(args); - if (result['help'] == true) { - print('UniversalChainBenchmark'); - print(parser.usage); - print('Example:'); - print(' dart run bin/main.dart --benchmark=chainFactory --chainCount=10 --nestingDepth=5 --format=csv'); - return; - } - final benchName = result['benchmark'] as String; - final isAll = benchName == 'all'; - final List allBenches = UniversalBenchmark.values; - final List benchesToRun = isAll - ? allBenches - : [parseEnum(benchName, UniversalBenchmark.values, UniversalBenchmark.chainSingleton)]; - final chainCounts = _parseIntList(result['chainCount'] as String); - final nestDepths = _parseIntList(result['nestingDepth'] as String); - final repeats = int.tryParse(result['repeat'] as String? ?? "") ?? 2; - final warmups = int.tryParse(result['warmup'] as String? ?? "") ?? 1; - final format = result['format'] as String; - - final results = >[]; - for (final bench in benchesToRun) { - final scenario = _toScenario(bench); - final mode = _toMode(bench); - for (final c in chainCounts) { - for (final d in nestDepths) { - BenchmarkResult benchResult; - if (scenario == UniversalScenario.asyncChain) { - final di = CherrypickDIAdapter(); - final benchAsync = UniversalChainAsyncBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - ); - benchResult = await BenchmarkRunner.runAsync( - benchmark: benchAsync, - warmups: warmups, - repeats: repeats, - ); - } else { - final di = CherrypickDIAdapter(); - final benchSync = UniversalChainBenchmark( - di, - chainCount: c, - nestingDepth: d, - mode: mode, - scenario: scenario, - ); - benchResult = await BenchmarkRunner.runSync( - benchmark: benchSync, - warmups: warmups, - repeats: repeats, - ); - } - 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 = 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.round(), - 'median_us': median.round(), - 'stddev_us': stddev.round(), - 'min_us': minVal.round(), - 'max_us': maxVal.round(), - 'trials': timings.length, - 'timings_us': timings.map((t) => t.round()).toList(), - 'memory_diff_kb': benchResult.memoryDiffKb, - 'delta_peak_kb': benchResult.deltaPeakKb, - 'peak_rss_kb': benchResult.peakRssKb, - }); - } - } - } - - final reportGenerators = { - 'pretty': PrettyReport(), - 'csv': CsvReport(), - 'json': JsonReport(), - }; - print(reportGenerators[format]?.render(results) ?? PrettyReport().render(results)); -} - -List _parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); + await BenchmarkCliRunner().run(args); +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_cherrypick/lib/cli/benchmark_cli.dart new file mode 100644 index 0000000..406be3e --- /dev/null +++ b/benchmark_cherrypick/lib/cli/benchmark_cli.dart @@ -0,0 +1,76 @@ +import 'dart:math'; + +import '../scenarios/universal_chain_module.dart'; +import 'report/pretty_report.dart'; +import 'report/csv_report.dart'; +import 'report/json_report.dart'; +import 'parser.dart'; +import 'runner.dart'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; +import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; + +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 (scenario == UniversalScenario.asyncChain) { + final di = CherrypickDIAdapter(); + final benchAsync = UniversalChainAsyncBenchmark(di, + chainCount: c, nestingDepth: d, mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + ); + } else { + final di = CherrypickDIAdapter(); + 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 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.round(), + 'median_us': median.round(), + 'stddev_us': stddev.round(), + 'min_us': minVal.round(), + 'max_us': maxVal.round(), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.round()).toList(), + 'memory_diff_kb': benchResult.memoryDiffKb, + 'delta_peak_kb': benchResult.deltaPeakKb, + 'peak_rss_kb': benchResult.peakRssKb, + }); + } + } + } + final reportGenerators = { + 'pretty': PrettyReport(), + 'csv': CsvReport(), + 'json': JsonReport(), + }; + print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results)); + } +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/parser.dart b/benchmark_cherrypick/lib/cli/parser.dart new file mode 100644 index 0000000..3d8450b --- /dev/null +++ b/benchmark_cherrypick/lib/cli/parser.dart @@ -0,0 +1,104 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; + +enum UniversalBenchmark { + registerSingleton, + chainSingleton, + chainFactory, + chainAsync, + named, + override, +} + +UniversalScenario toScenario(UniversalBenchmark b) { + switch (b) { + case UniversalBenchmark.registerSingleton: + return UniversalScenario.register; + case UniversalBenchmark.chainSingleton: + return UniversalScenario.chain; + case UniversalBenchmark.chainFactory: + return UniversalScenario.chain; + case UniversalBenchmark.chainAsync: + return UniversalScenario.asyncChain; + case UniversalBenchmark.named: + return UniversalScenario.named; + case UniversalBenchmark.override: + return UniversalScenario.override; + } +} + +UniversalBindingMode toMode(UniversalBenchmark b) { + switch (b) { + case UniversalBenchmark.registerSingleton: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.chainSingleton: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.chainFactory: + return UniversalBindingMode.factoryStrategy; + case UniversalBenchmark.chainAsync: + return UniversalBindingMode.asyncStrategy; + case UniversalBenchmark.named: + return UniversalBindingMode.singletonStrategy; + case UniversalBenchmark.override: + return UniversalBindingMode.singletonStrategy; + } +} + +T parseEnum(String value, List values, T defaultValue) { + return values.firstWhere( + (v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); +} + +List parseIntList(String s) => + s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); + +class BenchmarkCliConfig { + final List benchesToRun; + final List chainCounts; + final List nestDepths; + final int repeats; + final int warmups; + final String format; + BenchmarkCliConfig({ + required this.benchesToRun, + required this.chainCounts, + required this.nestDepths, + required this.repeats, + required this.warmups, + required this.format, + }); +} + +BenchmarkCliConfig parseBenchmarkCli(List args) { + final parser = ArgParser() + ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') + ..addOption('chainCount', abbr: 'c', defaultsTo: '10') + ..addOption('nestingDepth', abbr: 'd', defaultsTo: '5') + ..addOption('repeat', abbr: 'r', defaultsTo: '2') + ..addOption('warmup', abbr: 'w', defaultsTo: '1') + ..addOption('format', abbr: 'f', defaultsTo: 'pretty') + ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); + final result = parser.parse(args); + if (result['help'] == true) { + print(parser.usage); + exit(0); + } + final benchName = result['benchmark'] as String; + final isAll = benchName == 'all'; + final allBenches = UniversalBenchmark.values; + final benchesToRun = isAll + ? allBenches + : [parseEnum(benchName, allBenches, UniversalBenchmark.chainSingleton)]; + return BenchmarkCliConfig( + benchesToRun: benchesToRun, + chainCounts: parseIntList(result['chainCount'] as String), + nestDepths: parseIntList(result['nestingDepth'] as String), + repeats: int.tryParse(result['repeat'] as String? ?? "") ?? 2, + warmups: int.tryParse(result['warmup'] as String? ?? "") ?? 1, + format: result['format'] as String, + ); +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/report/csv_report.dart b/benchmark_cherrypick/lib/cli/report/csv_report.dart new file mode 100644 index 0000000..03e2449 --- /dev/null +++ b/benchmark_cherrypick/lib/cli/report/csv_report.dart @@ -0,0 +1,21 @@ +import 'report_generator.dart'; + +class CsvReport extends ReportGenerator { + @override + final List keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; + @override + String render(List> rows) { + final header = keys.join(','); + final lines = rows.map((r) => + keys.map((k) { + final v = r[k]; + if (v is List) return '"${v.join(';')}"'; + return (v ?? '').toString(); + }).join(',') + ).toList(); + return ([header] + lines).join('\n'); + } +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/report/json_report.dart b/benchmark_cherrypick/lib/cli/report/json_report.dart new file mode 100644 index 0000000..a21d662 --- /dev/null +++ b/benchmark_cherrypick/lib/cli/report/json_report.dart @@ -0,0 +1,10 @@ +import 'report_generator.dart'; + +class JsonReport extends ReportGenerator { + @override + List get keys => []; + @override + String render(List> rows) { + return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; + } +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/report/pretty_report.dart b/benchmark_cherrypick/lib/cli/report/pretty_report.dart new file mode 100644 index 0000000..66dd2de --- /dev/null +++ b/benchmark_cherrypick/lib/cli/report/pretty_report.dart @@ -0,0 +1,15 @@ +import 'report_generator.dart'; + +class PrettyReport extends ReportGenerator { + @override + final List keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; + @override + String render(List> rows) { + final header = keys.join('\t'); + final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); + return ([header] + lines).join('\n'); + } +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/report/report_generator.dart b/benchmark_cherrypick/lib/cli/report/report_generator.dart new file mode 100644 index 0000000..c46645d --- /dev/null +++ b/benchmark_cherrypick/lib/cli/report/report_generator.dart @@ -0,0 +1,4 @@ +abstract class ReportGenerator { + String render(List> results); + List get keys; +} \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/runner.dart b/benchmark_cherrypick/lib/cli/runner.dart new file mode 100644 index 0000000..b2ea3e1 --- /dev/null +++ b/benchmark_cherrypick/lib/cli/runner.dart @@ -0,0 +1,85 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; + +class BenchmarkResult { + final List timings; + final int memoryDiffKb; + final int deltaPeakKb; + final int peakRssKb; + BenchmarkResult({ + required this.timings, + required this.memoryDiffKb, + required this.deltaPeakKb, + required this.peakRssKb, + }); + factory BenchmarkResult.collect({ + required List timings, + required List rssValues, + required int memBefore, + }) { + final memAfter = ProcessInfo.currentRss; + final memDiffKB = ((memAfter - memBefore) / 1024).round(); + final peakRss = [...rssValues, memBefore].reduce(max); + final deltaPeakKb = ((peakRss - memBefore) / 1024).round(); + return BenchmarkResult( + timings: timings, + memoryDiffKb: memDiffKB, + deltaPeakKb: deltaPeakKb, + peakRssKb: (peakRss / 1024).round(), + ); + } +} + +class BenchmarkRunner { + static Future runSync({ + required UniversalChainBenchmark benchmark, + required int warmups, + required int repeats, + }) async { + final timings = []; + final rssValues = []; + for (int i = 0; i < warmups; i++) { + benchmark.setup(); + benchmark.run(); + benchmark.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + benchmark.setup(); + final sw = Stopwatch()..start(); + benchmark.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + benchmark.teardown(); + } + return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); + } + + static Future runAsync({ + required UniversalChainAsyncBenchmark benchmark, + required int warmups, + required int repeats, + }) async { + final timings = []; + final rssValues = []; + for (int i = 0; i < warmups; i++) { + await benchmark.setup(); + await benchmark.run(); + await benchmark.teardown(); + } + final memBefore = ProcessInfo.currentRss; + for (int i = 0; i < repeats; i++) { + await benchmark.setup(); + final sw = Stopwatch()..start(); + await benchmark.run(); + sw.stop(); + timings.add(sw.elapsedMicroseconds); + rssValues.add(ProcessInfo.currentRss); + await benchmark.teardown(); + } + return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); + } +} \ No newline at end of file From ea39b9d0e12cfe23793b5cb578b5f2d52f40151f Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:31:41 +0300 Subject: [PATCH 14/32] feat(report): align MarkdownReport columns for readable ASCII/markdown output --- .../lib/cli/report/markdown_report.dart | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 benchmark_cherrypick/lib/cli/report/markdown_report.dart diff --git a/benchmark_cherrypick/lib/cli/report/markdown_report.dart b/benchmark_cherrypick/lib/cli/report/markdown_report.dart new file mode 100644 index 0000000..c53e666 --- /dev/null +++ b/benchmark_cherrypick/lib/cli/report/markdown_report.dart @@ -0,0 +1,43 @@ +import 'report_generator.dart'; + +class MarkdownReport extends ReportGenerator { + @override + final List keys = [ + 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', + 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' + ]; + static const nameMap = { + 'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton', + 'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton', + 'Universal_UniversalBenchmark.chainFactory':'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync':'AsyncChain', + 'Universal_UniversalBenchmark.named':'Named', + 'Universal_UniversalBenchmark.override':'Override', + }; + @override + String render(List> rows) { + final headers = [ + 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' + ]; + final header = '| ' + headers.join(' | ') + ' |'; + final divider = '|' + List.filled(headers.length, '---').join('|') + '|'; + final dataRows = rows.map((r) { + final readableName = nameMap[r['benchmark']] ?? r['benchmark']; + return [ + readableName, + r['chainCount'], + r['nestingDepth'], + r['mean_us'], + r['median_us'], + r['stddev_us'], + r['min_us'], + r['max_us'], + r['trials'], + r['memory_diff_kb'], + r['delta_peak_kb'], + r['peak_rss_kb'], + ].map((cell) => cell.toString().padRight(10)).join(' | '); + }).map((row) => '| $row |').toList(); + return ([header, divider] + dataRows).join('\n'); + } +} From 3da71674d48640276bebba495fad555e8c9666d6 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:35:49 +0300 Subject: [PATCH 15/32] chore: fix current status, all implemented features and refactors --- benchmark_cherrypick/README.ru.md | 11 ++++++ .../lib/cli/benchmark_cli.dart | 3 ++ .../lib/cli/report/pretty_report.dart | 35 +++++++++++++++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index f8a6c6d..9d48f0b 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -89,6 +89,17 @@ class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { --- +| Benchmark | Chain Count | Depth | Mean (us) | Median | Stddev | Min | Max | N | ΔRSS(KB) | ΔPeak(KB) | PeakRSS(KB) | +|---|---|---|---|---|---|---|---|---|---|---|---| +| RegisterSingleton | 10 | 5 | 24 | 45 | 22 | 2 | 45 | 2 | 0 | 0 | 199232 | +| ChainSingleton | 10 | 5 | 41 | 45 | 4 | 37 | 45 | 2 | 0 | 0 | 199296 | +| ChainFactory | 10 | 5 | 43 | 50 | 8 | 35 | 50 | 2 | 0 | 0 | 199296 | +| AsyncChain | 10 | 5 | 49 | 50 | 2 | 47 | 50 | 2 | 0 | 0 | 199344 | +| Named | 10 | 5 | 1 | 1 | 0 | 1 | 1 | 2 | 0 | 0 | 199344 | +| Override | 10 | 5 | 2 | 2 | 1 | 1 | 2 | 2 | 0 | 0 | 199360 | + +--- + ## Лицензия MIT diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_cherrypick/lib/cli/benchmark_cli.dart index 406be3e..83d2485 100644 --- a/benchmark_cherrypick/lib/cli/benchmark_cli.dart +++ b/benchmark_cherrypick/lib/cli/benchmark_cli.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:benchmark_cherrypick/cli/report/markdown_report.dart'; + import '../scenarios/universal_chain_module.dart'; import 'report/pretty_report.dart'; import 'report/csv_report.dart'; @@ -70,6 +72,7 @@ class BenchmarkCliRunner { 'pretty': PrettyReport(), 'csv': CsvReport(), 'json': JsonReport(), + 'markdown': MarkdownReport(), }; print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results)); } diff --git a/benchmark_cherrypick/lib/cli/report/pretty_report.dart b/benchmark_cherrypick/lib/cli/report/pretty_report.dart index 66dd2de..70d80a7 100644 --- a/benchmark_cherrypick/lib/cli/report/pretty_report.dart +++ b/benchmark_cherrypick/lib/cli/report/pretty_report.dart @@ -6,10 +6,39 @@ class PrettyReport extends ReportGenerator { 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' ]; + + static const nameMap = { + 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', + 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', + 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', + 'Universal_UniversalBenchmark.named': 'Named', + 'Universal_UniversalBenchmark.override': 'Override', + }; + @override String render(List> rows) { - final header = keys.join('\t'); - final lines = rows.map((r) => keys.map((k) => (r[k] ?? '').toString()).join('\t')).toList(); + final headers = [ + 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' + ]; + final header = headers.join('\t'); + final lines = rows.map((r) { + final readableName = nameMap[r['benchmark']] ?? r['benchmark']; + return [ + readableName, + r['chainCount'], + r['nestingDepth'], + r['mean_us'], + r['median_us'], + r['stddev_us'], + r['min_us'], + r['max_us'], + r['trials'], + r['memory_diff_kb'], + r['delta_peak_kb'], + r['peak_rss_kb'], + ].join('\t'); + }).toList(); return ([header] + lines).join('\n'); } -} \ No newline at end of file +} From 1e6375f5ae613dc39d7aa4272a0f770a679606ad Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:41:08 +0300 Subject: [PATCH 16/32] refactor(report): round numeric values to 2 decimal places in MarkdownReport output --- .../lib/cli/report/markdown_report.dart | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/benchmark_cherrypick/lib/cli/report/markdown_report.dart b/benchmark_cherrypick/lib/cli/report/markdown_report.dart index c53e666..38e7c37 100644 --- a/benchmark_cherrypick/lib/cli/report/markdown_report.dart +++ b/benchmark_cherrypick/lib/cli/report/markdown_report.dart @@ -14,13 +14,12 @@ class MarkdownReport extends ReportGenerator { 'Universal_UniversalBenchmark.named':'Named', 'Universal_UniversalBenchmark.override':'Override', }; + @override String render(List> rows) { final headers = [ 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' ]; - final header = '| ' + headers.join(' | ') + ' |'; - final divider = '|' + List.filled(headers.length, '---').join('|') + '|'; final dataRows = rows.map((r) { final readableName = nameMap[r['benchmark']] ?? r['benchmark']; return [ @@ -36,8 +35,22 @@ class MarkdownReport extends ReportGenerator { r['memory_diff_kb'], r['delta_peak_kb'], r['peak_rss_kb'], - ].map((cell) => cell.toString().padRight(10)).join(' | '); - }).map((row) => '| $row |').toList(); - return ([header, divider] + dataRows).join('\n'); + ].map((cell) => cell.toString()).toList(); + }).toList(); + + // Вычислить ширину каждой колонки + final all = [headers] + dataRows; + final widths = List.generate(headers.length, (i) { + return all.map((row) => row[i].length).reduce((a, b) => a > b ? a : b); + }); + + String rowToLine(List row, {String sep = ' | '}) => + '| ' + List.generate(row.length, (i) => row[i].padRight(widths[i])).join(sep) + ' |'; + + final headerLine = rowToLine(headers); + final divider = '| ' + widths.map((w) => '-' * w).join(' | ') + ' |'; + final lines = dataRows.map(rowToLine).toList(); + + return ([headerLine, divider] + lines).join('\n'); } -} +} \ No newline at end of file From 01d82e1cd30036356f7ccc6ef7f0c364364e7fde Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 22:53:33 +0300 Subject: [PATCH 17/32] feat(report): add legend to MarkdownReport output with explanation of columns --- benchmark_cherrypick/README.md | 11 ++++++++++ .../lib/cli/benchmark_cli.dart | 12 +++++------ .../lib/cli/report/markdown_report.dart | 21 ++++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index 629e0ce..2b739ac 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -89,6 +89,17 @@ class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { --- +| Benchmark | Chain Count | Depth | Mean (us) | Median | Stddev | Min | Max | N | ΔRSS(KB) | ΔPeak(KB) | PeakRSS(KB) | +| ----------------- | ----------- | ----- | --------- | ------ | ------ | ----- | ----- | - | -------- | --------- | ----------- | +| RegisterSingleton | 10 | 5 | 23.00 | 44.00 | 21.00 | 2.00 | 44.00 | 2 | 16 | 16 | 200400 | +| ChainSingleton | 10 | 5 | 42.50 | 51.00 | 8.50 | 34.00 | 51.00 | 2 | 64 | 64 | 200592 | +| ChainFactory | 10 | 5 | 42.00 | 48.00 | 6.00 | 36.00 | 48.00 | 2 | 64 | 64 | 200688 | +| AsyncChain | 10 | 5 | 49.00 | 52.00 | 3.00 | 46.00 | 52.00 | 2 | 0 | 0 | 200784 | +| Named | 10 | 5 | 1.00 | 1.00 | 0.00 | 1.00 | 1.00 | 2 | 0 | 0 | 200784 | +| Override | 10 | 5 | 1.50 | 2.00 | 0.50 | 1.00 | 2.00 | 2 | 0 | 0 | 200800 | + +--- + ## License MIT diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_cherrypick/lib/cli/benchmark_cli.dart index 83d2485..9b155d8 100644 --- a/benchmark_cherrypick/lib/cli/benchmark_cli.dart +++ b/benchmark_cherrypick/lib/cli/benchmark_cli.dart @@ -54,13 +54,13 @@ class BenchmarkCliRunner { 'benchmark': 'Universal_$bench', 'chainCount': c, 'nestingDepth': d, - 'mean_us': mean.round(), - 'median_us': median.round(), - 'stddev_us': stddev.round(), - 'min_us': minVal.round(), - 'max_us': maxVal.round(), + '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.round()).toList(), + '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_cherrypick/lib/cli/report/markdown_report.dart b/benchmark_cherrypick/lib/cli/report/markdown_report.dart index 38e7c37..5e58f66 100644 --- a/benchmark_cherrypick/lib/cli/report/markdown_report.dart +++ b/benchmark_cherrypick/lib/cli/report/markdown_report.dart @@ -45,12 +45,27 @@ class MarkdownReport extends ReportGenerator { }); String rowToLine(List row, {String sep = ' | '}) => - '| ' + List.generate(row.length, (i) => row[i].padRight(widths[i])).join(sep) + ' |'; + '| ${List.generate(row.length, (i) => row[i].padRight(widths[i])).join(sep)} |'; final headerLine = rowToLine(headers); - final divider = '| ' + widths.map((w) => '-' * w).join(' | ') + ' |'; + final divider = '| ${widths.map((w) => '-' * w).join(' | ')} |'; final lines = dataRows.map(rowToLine).toList(); - return ([headerLine, divider] + lines).join('\n'); + final legend = ''' + > **Legend:** + > `Benchmark` – Test name + > `Chain Count` – Number of independent chains + > `Depth` – Depth of each chain + > `Mean (us)` – Average time per run (microseconds) + > `Median` – Median time per run + > `Stddev` – Standard deviation + > `Min`, `Max` – Min/max run time + > `N` – Number of measurements + > `ΔRSS(KB)` – Change in process memory (KB) + > `ΔPeak(KB)` – Change in peak RSS (KB) + > `PeakRSS(KB)` – Max observed RSS memory (KB) + '''; + + return '$legend\n\n${([headerLine, divider] + lines).join('\n')}' ; } } \ No newline at end of file From 134fc5207a52a02ddd16eb9cf6ee2c9ee54b1735 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 23:15:28 +0300 Subject: [PATCH 18/32] Add English documentation comments to all benchmark_cherrypick source files (adapters, scenarios, CLI, reporters, runner) --- .../lib/cli/benchmark_cli.dart | 6 +++++ benchmark_cherrypick/lib/cli/parser.dart | 20 +++++++++++++++ .../lib/cli/report/csv_report.dart | 3 +++ .../lib/cli/report/json_report.dart | 3 +++ .../lib/cli/report/markdown_report.dart | 9 ++++++- .../lib/cli/report/pretty_report.dart | 6 +++++ .../lib/cli/report/report_generator.dart | 5 ++++ benchmark_cherrypick/lib/cli/runner.dart | 11 ++++++++ .../lib/di_adapters/cherrypick_adapter.dart | 7 ++++++ .../lib/di_adapters/di_adapter.dart | 13 ++++++++++ .../lib/scenarios/universal_chain_module.dart | 25 +++++++++++++++++++ .../lib/scenarios/universal_service.dart | 7 ++++++ 12 files changed, 114 insertions(+), 1 deletion(-) diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_cherrypick/lib/cli/benchmark_cli.dart index 9b155d8..61ca734 100644 --- a/benchmark_cherrypick/lib/cli/benchmark_cli.dart +++ b/benchmark_cherrypick/lib/cli/benchmark_cli.dart @@ -12,7 +12,13 @@ import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; +/// Command-line interface (CLI) runner for benchmarks. +/// +/// Parses CLI arguments, orchestrates benchmarks for different +/// scenarios and configurations, collects results, and generates reports +/// in the desired output format. class BenchmarkCliRunner { + /// Runs benchmarks based on CLI [args], configuring different test scenarios. Future run(List args) async { final config = parseBenchmarkCli(args); final results = >[]; diff --git a/benchmark_cherrypick/lib/cli/parser.dart b/benchmark_cherrypick/lib/cli/parser.dart index 3d8450b..11ad208 100644 --- a/benchmark_cherrypick/lib/cli/parser.dart +++ b/benchmark_cherrypick/lib/cli/parser.dart @@ -3,15 +3,23 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; +/// Enum describing all supported Universal DI benchmark types. enum UniversalBenchmark { + /// Simple singleton registration benchmark registerSingleton, + /// Chain of singleton dependencies chainSingleton, + /// Chain using factories chainFactory, + /// Async chain resolution chainAsync, + /// Named registration benchmark named, + /// Override/child-scope benchmark override, } +/// Maps [UniversalBenchmark] to the scenario enum for DI chains. UniversalScenario toScenario(UniversalBenchmark b) { switch (b) { case UniversalBenchmark.registerSingleton: @@ -29,6 +37,7 @@ UniversalScenario toScenario(UniversalBenchmark b) { } } +/// Maps benchmark to registration mode (singleton/factory/async). UniversalBindingMode toMode(UniversalBenchmark b) { switch (b) { case UniversalBenchmark.registerSingleton: @@ -46,6 +55,7 @@ UniversalBindingMode toMode(UniversalBenchmark b) { } } +/// Utility to parse a string into its corresponding enum value [T]. T parseEnum(String value, List values, T defaultValue) { return values.firstWhere( (v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(), @@ -53,15 +63,23 @@ T parseEnum(String value, List values, T defaultValue) { ); } +/// Parses comma-separated integer list from [s]. List parseIntList(String s) => s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); +/// CLI config describing what and how to benchmark. class BenchmarkCliConfig { + /// Benchmarks enabled to run (scenarios). final List benchesToRun; + /// List of chain counts (parallel, per test). final List chainCounts; + /// List of nesting depths (max chain length, per test). final List nestDepths; + /// How many times to repeat each trial. final int repeats; + /// How many times to warm-up before measuring. final int warmups; + /// Output report format. final String format; BenchmarkCliConfig({ required this.benchesToRun, @@ -73,6 +91,8 @@ class BenchmarkCliConfig { }); } +/// Parses CLI arguments [args] into a [BenchmarkCliConfig]. +/// Supports --benchmark, --chainCount, --nestingDepth, etc. BenchmarkCliConfig parseBenchmarkCli(List args) { final parser = ArgParser() ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') diff --git a/benchmark_cherrypick/lib/cli/report/csv_report.dart b/benchmark_cherrypick/lib/cli/report/csv_report.dart index 03e2449..6379889 100644 --- a/benchmark_cherrypick/lib/cli/report/csv_report.dart +++ b/benchmark_cherrypick/lib/cli/report/csv_report.dart @@ -1,11 +1,14 @@ import 'report_generator.dart'; +/// Generates a CSV-formatted report for benchmark results. class CsvReport extends ReportGenerator { + /// List of all keys/columns to include in the CSV output. @override final List keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb' ]; + /// Renders rows as a CSV table string. @override String render(List> rows) { final header = keys.join(','); diff --git a/benchmark_cherrypick/lib/cli/report/json_report.dart b/benchmark_cherrypick/lib/cli/report/json_report.dart index a21d662..fb75d67 100644 --- a/benchmark_cherrypick/lib/cli/report/json_report.dart +++ b/benchmark_cherrypick/lib/cli/report/json_report.dart @@ -1,8 +1,11 @@ import 'report_generator.dart'; +/// Generates a JSON-formatted report for benchmark results. class JsonReport extends ReportGenerator { + /// No specific keys; outputs all fields in raw map. @override List get keys => []; + /// Renders all result rows as a pretty-printed JSON array. @override String render(List> rows) { return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; diff --git a/benchmark_cherrypick/lib/cli/report/markdown_report.dart b/benchmark_cherrypick/lib/cli/report/markdown_report.dart index 5e58f66..cf97ecc 100644 --- a/benchmark_cherrypick/lib/cli/report/markdown_report.dart +++ b/benchmark_cherrypick/lib/cli/report/markdown_report.dart @@ -1,11 +1,17 @@ import 'report_generator.dart'; +/// Generates a Markdown-formatted report for benchmark results. +/// +/// Displays result rows as a visually clear Markdown table including a legend for all metrics. class MarkdownReport extends ReportGenerator { + /// List of columns (keys) to show in the Markdown table. @override final List keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' ]; + + /// Friendly display names for each benchmark type. static const nameMap = { 'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton', 'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton', @@ -15,6 +21,7 @@ class MarkdownReport extends ReportGenerator { 'Universal_UniversalBenchmark.override':'Override', }; + /// Renders all results as a formatted Markdown table with aligned columns and a legend. @override String render(List> rows) { final headers = [ @@ -38,7 +45,7 @@ class MarkdownReport extends ReportGenerator { ].map((cell) => cell.toString()).toList(); }).toList(); - // Вычислить ширину каждой колонки + // Calculate column width for pretty alignment final all = [headers] + dataRows; final widths = List.generate(headers.length, (i) { return all.map((row) => row[i].length).reduce((a, b) => a > b ? a : b); diff --git a/benchmark_cherrypick/lib/cli/report/pretty_report.dart b/benchmark_cherrypick/lib/cli/report/pretty_report.dart index 70d80a7..36688ef 100644 --- a/benchmark_cherrypick/lib/cli/report/pretty_report.dart +++ b/benchmark_cherrypick/lib/cli/report/pretty_report.dart @@ -1,12 +1,17 @@ import 'report_generator.dart'; +/// Generates a human-readable, tab-delimited report for benchmark results. +/// +/// Used for terminal and log output; shows each result as a single line with labeled headers. class PrettyReport extends ReportGenerator { + /// List of columns to output in the pretty report. @override final List keys = [ 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' ]; + /// Mappings from internal benchmark IDs to display names. static const nameMap = { 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', @@ -16,6 +21,7 @@ class PrettyReport extends ReportGenerator { 'Universal_UniversalBenchmark.override': 'Override', }; + /// Renders the results as a header + tab-separated value table. @override String render(List> rows) { final headers = [ diff --git a/benchmark_cherrypick/lib/cli/report/report_generator.dart b/benchmark_cherrypick/lib/cli/report/report_generator.dart index c46645d..59a1e98 100644 --- a/benchmark_cherrypick/lib/cli/report/report_generator.dart +++ b/benchmark_cherrypick/lib/cli/report/report_generator.dart @@ -1,4 +1,9 @@ +/// Abstract base for generating benchmark result reports in different formats. +/// +/// Subclasses implement [render] to output results, and [keys] to define columns (if any). abstract class ReportGenerator { + /// Renders the given [results] as a formatted string (table, markdown, csv, etc). String render(List> results); + /// List of output columns/keys included in the export (or [] for auto/all). List get keys; } \ No newline at end of file diff --git a/benchmark_cherrypick/lib/cli/runner.dart b/benchmark_cherrypick/lib/cli/runner.dart index b2ea3e1..5687dba 100644 --- a/benchmark_cherrypick/lib/cli/runner.dart +++ b/benchmark_cherrypick/lib/cli/runner.dart @@ -3,10 +3,15 @@ import 'dart:math'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; +/// Holds the results for a single benchmark execution. class BenchmarkResult { + /// List of timings for each run (in microseconds). final List timings; + /// Difference in memory (RSS, in KB) after running. final int memoryDiffKb; + /// Difference between peak RSS and initial RSS (in KB). final int deltaPeakKb; + /// Peak RSS memory observed (in KB). final int peakRssKb; BenchmarkResult({ required this.timings, @@ -14,6 +19,7 @@ class BenchmarkResult { required this.deltaPeakKb, required this.peakRssKb, }); + /// Computes a BenchmarkResult instance from run timings and memory data. factory BenchmarkResult.collect({ required List timings, required List rssValues, @@ -32,7 +38,10 @@ class BenchmarkResult { } } +/// Static methods to execute and time benchmarks for DI containers. class BenchmarkRunner { + /// Runs a synchronous benchmark ([UniversalChainBenchmark]) for a given number of [warmups] and [repeats]. + /// Collects execution time and observed memory. static Future runSync({ required UniversalChainBenchmark benchmark, required int warmups, @@ -58,6 +67,8 @@ class BenchmarkRunner { return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); } + /// Runs an asynchronous benchmark ([UniversalChainAsyncBenchmark]) for a given number of [warmups] and [repeats]. + /// Collects execution time and observed memory. static Future runAsync({ required UniversalChainAsyncBenchmark benchmark, required int warmups, diff --git a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart index e9489ec..55bcdfd 100644 --- a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart @@ -1,6 +1,11 @@ import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; +/// DIAdapter implementation for the CherryPick DI library. +/// +/// Wraps a CherryPick [Scope] and provides methods +/// to setup modules, resolve dependencies, teardown, +/// and open nested sub-scopes for benchmarking. class CherrypickDIAdapter implements DIAdapter { Scope? _scope; @@ -37,6 +42,8 @@ class CherrypickDIAdapter implements DIAdapter { } } +/// Internal adapter for a CherryPick sub-scope. +/// Used for simulating child/override DI scopes in benchmarks. class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { final Scope _subScope; _CherrypickSubScopeAdapter(this._subScope); diff --git a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart index de818e3..6a134eb 100644 --- a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart @@ -1,9 +1,22 @@ import 'package:cherrypick/cherrypick.dart'; +/// Abstraction for Dependency Injection (DI) Adapter. +/// +/// Provides a uniform interface to setup, resolve, and teardown DI containers/modules +/// and open sub-scopes to benchmark them under different libraries. abstract class DIAdapter { + /// Installs the provided modules into the DI container. void setupModules(List modules); + + /// Resolves an instance of type [T] by optional [named] tag. T resolve({String? named}); + + /// Asynchronously resolves an instance of type [T] by optional [named] tag. Future resolveAsync({String? named}); + + /// Tears down or disposes of the DI container. void teardown(); + + /// Opens a child DI sub-scope, useful for override/child-scope benchmarks. DIAdapter openSubScope(String name); } diff --git a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart index dd89644..c006581 100644 --- a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart +++ b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart @@ -1,26 +1,47 @@ import 'package:cherrypick/cherrypick.dart'; import 'universal_service.dart'; +/// Enum to represent the DI registration/binding mode. enum UniversalBindingMode { + /// Singleton/provider binding. singletonStrategy, + + /// Factory-based binding. factoryStrategy, + + /// Async-based binding. asyncStrategy, } +/// Enum to represent which scenario is constructed for the benchmark. enum UniversalScenario { + /// Single registration. register, + /// Chain of dependencies. chain, + /// Named registrations. named, + /// Child-scope override scenario. override, + /// Asynchronous chain scenario. asyncChain, } +/// Test module that generates a chain of service bindings for benchmarking. +/// +/// Configurable by chain count, nesting depth, binding mode, and scenario +/// to support various DI performance tests (singleton, factory, async, etc). class UniversalChainModule extends Module { + /// Number of chains to create. final int chainCount; + /// Depth of each chain. final int nestingDepth; + /// How modules are registered (factory/singleton/async). final UniversalBindingMode bindingMode; + /// Which di scenario to generate (chained, named, etc). final UniversalScenario scenario; + /// Constructs a configured test DI module for the benchmarks. UniversalChainModule({ required this.chainCount, required this.nestingDepth, @@ -31,6 +52,7 @@ class UniversalChainModule extends Module { @override void builder(Scope currentScope) { if (scenario == UniversalScenario.asyncChain) { + // Generate async chain with singleton async bindings. for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { final chain = chainIndex + 1; @@ -56,15 +78,18 @@ class UniversalChainModule extends Module { switch (scenario) { case UniversalScenario.register: + // Simple singleton registration. bind() .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) .singleton(); break; case UniversalScenario.named: + // Named factory registration for two distinct objects. bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); break; case UniversalScenario.chain: + // Chain of nested services, with dependency on previous level by name. for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { final chain = chainIndex + 1; diff --git a/benchmark_cherrypick/lib/scenarios/universal_service.dart b/benchmark_cherrypick/lib/scenarios/universal_service.dart index f6ff736..910201f 100644 --- a/benchmark_cherrypick/lib/scenarios/universal_service.dart +++ b/benchmark_cherrypick/lib/scenarios/universal_service.dart @@ -1,10 +1,17 @@ +/// Base interface for any universal service in the benchmarks. +/// +/// Represents an object in the dependency chain with an identifiable value +/// and (optionally) a dependency on a previous service in the chain. abstract class UniversalService { + /// String ID for this service instance (e.g. chain/level info). final String value; + /// Optional reference to dependency service in the chain. final UniversalService? dependency; UniversalService({required this.value, this.dependency}); } +/// Default implementation for [UniversalService] used in service chains. class UniversalServiceImpl extends UniversalService { UniversalServiceImpl({required super.value, super.dependency}); } \ No newline at end of file From 352442e52d0496720a06d62fee8043fe5184cd72 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 23:19:37 +0300 Subject: [PATCH 19/32] Update README.md with current benchmark scenarios, CLI options, and report formats (EN) --- benchmark_cherrypick/README.md | 135 +++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 59 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index 2b739ac..cdc83f0 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -1,102 +1,119 @@ # benchmark_cherrypick -Benchmarks for the performance and features of the cherrypick (core) DI container. +_Benchmark suite for cherrypick DI container and its features._ -## Scenarios +## Overview -- **RegisterAndResolve**: Basic registration and resolution of a dependency. -- **ChainSingleton** (A->B->C, singleton): Deep dependency chain, all as singletons. -- **ChainFactory** (A->B->C, factory): Dependency chain with factory bindings (new instance per request). -- **NamedResolve** (by name): Resolving a named dependency among several implementations. -- **AsyncChain** (A->B->C, async): Asynchronous dependency chain. -- **ScopeOverride** (child overrides parent): Overriding a dependency in a child scope over a parent. +This package provides comprehensive benchmarks for the [cherrypick](https://github.com/) dependency injection core and comparable DI scenarios. It includes a CLI tool for running a matrix of synthetic scenarios—covering depth and breadth, named resolutions, scope overrides, async chains, memory usage and more. -## Features +**Key Features:** +- Declarative matrix runs (chain count, nesting depth, scenario, repeats) +- CLI tool with flexible configuration +- Multiple report formats: pretty table, CSV, JSON, Markdown +- Memory and runtime statistics (mean, median, stddev, min, max, memory diffs) +- Built-in and extensible scenarios (singletons, factories, named, async, overrides) +- Easy to extend with your own modules/adapters -- **Unified benchmark structure** -- **Flexible CLI parameterization (chain length, depth, repeats, warmup, scenario selection, format)** -- **Automatic matrix/mass run for sets of parameters** -- **Statistics: mean, median, stddev, min, max for each scenario** -- **Memory metrics: memory_diff_kb (total diff), delta_peak_kb (max growth), peak_rss_kb (absolute peak)** -- **Pretty-table, CSV, and JSON output** -- **Warmup runs before timing for better result stability** +--- -## How to run +## Benchmark Scenarios -1. Get dependencies: +- **RegisterSingleton**: Registers and resolves a singleton dependency +- **ChainSingleton**: Resolves a deep chain of singleton dependencies (A→B→C...) +- **ChainFactory**: Resolves a deep chain using factory bindings (new instance each time) +- **AsyncChain**: Resolves an async dependency chain (async providers) +- **Named**: Resolves a named dependency from several implementations +- **Override**: Resolves a dependency overridden in a child scope + +--- + +## How to Run + +1. **Get dependencies:** ```shell dart pub get ``` -2. Run all benchmarks (defaults: single parameter set, repeat=5, warmup=2): +2. **Run all benchmarks (default single configuration, 2 warmups, 2 repeats):** ```shell dart run bin/main.dart ``` -### Custom parameters +3. **Show available CLI options:** + ```shell + dart run bin/main.dart --help + ``` -- Matrix run (CSV, 7 repeats, 3 warmups): +### CLI Parameters + +- `--benchmark, -b` — Benchmark scenario: + `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` (default: all) +- `--chainCount, -c` — Comma-separated chain counts, e.g. `10,100` +- `--nestingDepth, -d` — Comma-separated chain depths, e.g. `5,10` +- `--repeat, -r` — Number of measurement runs per scenario (default: 2) +- `--warmup, -w` — Warmup runs before measuring (default: 1) +- `--format, -f` — Output format: `pretty`, `csv`, `json`, `markdown` (default: pretty) +- `--help, -h` — Show usage + +### Examples + +- **Matrix run:** ```shell - dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --repeat=7 --warmup=3 --format=csv + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=5 --warmup=2 --format=markdown ``` -- Run only the named resolve scenario: +- **Run just the named scenario:** ```shell - dart run bin/main.dart --benchmark=named --repeat=3 --warmup=1 + dart run bin/main.dart --benchmark=named --repeat=3 ``` -- See available CLI flags: - ```shell - dart run bin/main.dart --help - ``` +### Example Output (Markdown) -#### CLI options - -- `--benchmark` (`-b`) — Scenario: - `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (default: all) -- `--chainCount` (`-c`) — Comma-separated chain lengths. E.g. `10,100` -- `--nestingDepth` (`-d`) — Comma-separated chain depths. E.g. `5,10` -- `--repeat` (`-r`) — How many times to measure each scenario (`default: 5`) -- `--warmup` (`-w`) — How many warmup runs before actual timing (`default: 2`) -- `--format` (`-f`) — Output: `pretty`, `csv`, `json` (default: pretty) -- `--help` (`-h`) — Print help - -#### Example output (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us,memory_diff_kb,delta_peak_kb,peak_rss_kb -ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000",-64,0,200064 +| Benchmark | Chain Count | Depth | Mean (us) | ... | PeakRSS(KB) | +|------------------|-------------|-------|-----------| ... |-------------| +| ChainSingleton | 10 | 5 | 2450000 | ... | 200064 | ``` --- -## Add your own benchmark +## Report Formats -1. Create a Dart file with a class inheriting from `BenchmarkBase` or `AsyncBenchmarkBase`. -2. Use the `BenchmarkWithScope` mixin for automatic Scope management if needed. -3. Add your benchmark to bin/main.dart for selection via CLI. +- **pretty** — Tab-delimited table (human-friendly) +- **csv** — Machine-friendly, for spreadsheets/scripts +- **json** — For automation, data pipelines +- **markdown** — Markdown table for docs/wikis/issues --- -## Contributor example +## How to Add Your Own Benchmark +1. Implement a class extending `BenchmarkBase` (sync case) or `AsyncBenchmarkBase`. +2. Configure scenario modules/services using the DI adapter interface. +3. Add scenario selection logic if needed (see bin/main.dart). +4. Optionally extend reporters or adapters for new DI libraries. + +Example minimal benchmark: ```dart -class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { +class MyBenchmark extends BenchmarkBase { MyBenchmark() : super('My custom'); - @override void setup() => setupScope([MyModule()]); - @override void run() { scope.resolve(); } - @override void teardown() => teardownScope(); + @override void setup() { /* setup test DI modules */ } + @override void run() { /* resolve or invoke dependency chain */ } + @override void teardown() { /* cleanup if needed */ } } ``` +To plug in a new DI library, implement DIAdapter and register it in CLI. + --- -| Benchmark | Chain Count | Depth | Mean (us) | Median | Stddev | Min | Max | N | ΔRSS(KB) | ΔPeak(KB) | PeakRSS(KB) | -| ----------------- | ----------- | ----- | --------- | ------ | ------ | ----- | ----- | - | -------- | --------- | ----------- | -| RegisterSingleton | 10 | 5 | 23.00 | 44.00 | 21.00 | 2.00 | 44.00 | 2 | 16 | 16 | 200400 | -| ChainSingleton | 10 | 5 | 42.50 | 51.00 | 8.50 | 34.00 | 51.00 | 2 | 64 | 64 | 200592 | -| ChainFactory | 10 | 5 | 42.00 | 48.00 | 6.00 | 36.00 | 48.00 | 2 | 64 | 64 | 200688 | -| AsyncChain | 10 | 5 | 49.00 | 52.00 | 3.00 | 46.00 | 52.00 | 2 | 0 | 0 | 200784 | -| Named | 10 | 5 | 1.00 | 1.00 | 0.00 | 1.00 | 1.00 | 2 | 0 | 0 | 200784 | -| Override | 10 | 5 | 1.50 | 2.00 | 0.50 | 1.00 | 2.00 | 2 | 0 | 0 | 200800 | +## Metrics Collected + +All benchmarks record: +- **Time** (microseconds): mean, median, stddev, min, max, timings +- **Memory**: + - memory_diff_kb — change in RSS (KB) + - delta_peak_kb — change in peak RSS (KB) + - peak_rss_kb — absolute peak RSS (KB) --- From b72dec99446cd49e24f17b6e03ec0958fdf787f1 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 6 Aug 2025 23:22:05 +0300 Subject: [PATCH 20/32] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20README.ru.md:=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=81=D1=86=D0=B5=D0=BD=D0=B0=D1=80=D0=B8=D0=B8,=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B=20CLI,?= =?UTF-8?q?=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D1=8B=20=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D1=91=D1=82=D0=BE=D0=B2,=20=D0=B8=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BA=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmark_cherrypick/README.ru.md | 135 +++++++++++++++++------------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index 9d48f0b..01c9b2f 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -1,102 +1,117 @@ # benchmark_cherrypick -Бенчмарки производительности и возможностей DI-контейнера cherrypick (core). +_Набор бенчмарков для анализа производительности и особенностей DI-контейнера cherrypick._ -## Сценарии +## Описание -- **RegisterAndResolve**: базовая регистрация и разрешение зависимости. -- **ChainSingleton** (A->B->C, singleton): длинная цепочка зависимостей, все как singleton. -- **ChainFactory** (A->B->C, factory): цепочка зависимостей через factory (новый объект на каждый запрос). -- **NamedResolve** (by name): разрешение зависимости по имени среди нескольких реализаций. -- **AsyncChain** (A->B->C, async): асинхронная цепочка зависимостей. -- **ScopeOverride** (child overrides parent): перекрытие зависимости в дочернем scope относительно родителя. +Этот пакет предоставляет комплексные синтетические бенчмарки для DI-контейнера [cherrypick](https://github.com/). CLI-интерфейс позволяет запускать сценарии с разной глубиной, шириной, вариантами разрешения (singletons, factories, named, override, async), снимая статистику по времени и памяти, генерируя отчёты в различных форматах. -## Возможности +**Особенности:** +- Матричный запуск (chain count, nesting depth, сценарий, повторы) +- Гибкая настройка CLI +- Много форматов отчётов: таблица, CSV, JSON, Markdown +- Подсчет времени и памяти (mean, median, stddev, min, max, разница RSS/пик) +- Встроенные и легко расширяемые сценарии (singletons, factories, async, named, override) +- Механизм подключения других DI-контейнеров через адаптеры -- **Унифицированная структура бенчмарков** -- **Гибкая параметризация CLI (chainCount, nestingDepth, repeats, warmup, сценарий, формат)** -- **Автоматический матричный запуск для наборов параметров** -- **Статистика: среднее, медиана, stddev, min, max для каждого сценария** -- **Память: memory_diff_kb (итоговая разница), delta_peak_kb (максимальный рост), peak_rss_kb (абсолютный пик)** -- **Вывод в таблицу, CSV или JSON** -- **Прогревочные запуски до замера времени для стабильности** +--- + +## Сценарии бенчмарков + +- **RegisterSingleton**: Регистрация и разрешение singleton-зависимости +- **ChainSingleton**: Глубокая цепочка singleton-зависимостей (A→B→C...) +- **ChainFactory**: Цепочка с factory (новый объект при каждом разрешении) +- **AsyncChain**: Асинхронная цепочка зависимостей +- **Named**: Разрешение зависимости по имени среди нескольких реализаций +- **Override**: Разрешение зависимости, перекрытой в дочернем scope + +--- ## Как запустить -1. Установить зависимости: +1. **Установите зависимости:** ```shell dart pub get ``` -2. Запустить все бенчмарки (по умолчанию: одни значения, repeat=5, warmup=2): +2. **Запустите все бенчмарки (по умолчанию: одна комбинация, 2 прогрева, 2 повтора):** ```shell dart run bin/main.dart ``` -### Пользовательские параметры +3. **Показать все CLI-параметры:** + ```shell + dart run bin/main.dart --help + ``` -- Матричный прогон (csv, 7 повторов, 3 прогрева): +### CLI-параметры + +- `--benchmark, -b` — Сценарий: + `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` (по умолчанию: all) +- `--chainCount, -c` — Длины цепочек через запятую (`10,100`) +- `--nestingDepth, -d` — Глубины цепочек через запятую (`5,10`) +- `--repeat, -r` — Повторов на сценарий (по умолчанию 2) +- `--warmup, -w` — Прогревов до замера (по умолчанию 1) +- `--format, -f` — Формат отчёта: `pretty`, `csv`, `json`, `markdown` (по умолчанию pretty) +- `--help, -h` — Показать справку + +### Примеры запуска + +- **Матричный запуск:** ```shell - dart run bin/main.dart --benchmark=chain_singleton --chainCount=10,100 --nestingDepth=5,10 --repeat=7 --warmup=3 --format=csv + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=5 --warmup=2 --format=markdown ``` -- Только сценарий с именованным разрешением: +- **Только сценарий с именованным разрешением:** ```shell - dart run bin/main.dart --benchmark=named --repeat=3 --warmup=1 + dart run bin/main.dart --benchmark=named --repeat=3 ``` -- Посмотреть все флаги CLI: - ```shell - dart run bin/main.dart --help - ``` +### Пример вывода (Markdown): -#### Опции CLI - -- `--benchmark` (`-b`) — Сценарий: - `register`, `chain_singleton`, `chain_factory`, `named`, `override`, `async_chain`, `all` (по умолчанию all) -- `--chainCount` (`-c`) — Длины цепочек через запятую. Напр: `10,100` -- `--nestingDepth` (`-d`) — Глубины цепочек через запятую. Напр: `5,10` -- `--repeat` (`-r`) — Сколько раз мерить каждую конфигурацию (`по умолчанию: 5`) -- `--warmup` (`-w`) — Сколько прогревочных запусков до замера времени (`по умолчанию: 2`) -- `--format` (`-f`) — Вывод: `pretty`, `csv`, `json` (по умолчанию pretty) -- `--help` (`-h`) — Показать справку - -#### Пример вывода (`--format=csv`) ``` -benchmark,chainCount,nestingDepth,mean_us,median_us,stddev_us,min_us,max_us,trials,timings_us,memory_diff_kb,delta_peak_kb,peak_rss_kb -ChainSingleton,10,5,2450000,2440000,78000,2290000,2580000,5,"2440000;2460000;2450000;2580000;2290000",-64,0,200064 +| Benchmark | Chain Count | Depth | Mean (us) | ... | PeakRSS(KB) | +|------------------|-------------|-------|-----------| ... |-------------| +| ChainSingleton | 10 | 5 | 2450000 | ... | 200064 | ``` --- +## Форматы отчёта + +- **pretty** — табличный человекочитаемый вывод +- **csv** — удобно для Excel и анализа скриптами +- **json** — для автотестов и аналитики +- **markdown** — Markdown-таблица (в Issues/Wiki) + +--- + ## Как добавить свой бенчмарк -1. Создайте Dart-файл с классом, унаследованным от `BenchmarkBase` или `AsyncBenchmarkBase`. -2. Используйте миксин `BenchmarkWithScope` для управления Scope (если нужно). -3. Добавьте ваш бенчмарк в bin/main.dart для запуска через CLI. - ---- - -## Пример для контрибуторов +1. Создайте класс на основе `BenchmarkBase` (для sync) или `AsyncBenchmarkBase` (для async) +2. Настройте DI через адаптер, создайте нужный модуль/сценарий +3. Добавьте новый случай в bin/main.dart для CLI +4. Для поддержки других DI-контейнеров реализуйте свой DIAdapter +Пример минимального бенчмарка: ```dart -class MyBenchmark extends BenchmarkBase with BenchmarkWithScope { +class MyBenchmark extends BenchmarkBase { MyBenchmark() : super('My custom'); - @override void setup() => setupScope([MyModule()]); - @override void run() { scope.resolve(); } - @override void teardown() => teardownScope(); + @override void setup() {/* настройка DI, создание цепочки */} + @override void run() {/* разрешение/запуск */} + @override void teardown() {/* очистка, если нужно */} } ``` --- -| Benchmark | Chain Count | Depth | Mean (us) | Median | Stddev | Min | Max | N | ΔRSS(KB) | ΔPeak(KB) | PeakRSS(KB) | -|---|---|---|---|---|---|---|---|---|---|---|---| -| RegisterSingleton | 10 | 5 | 24 | 45 | 22 | 2 | 45 | 2 | 0 | 0 | 199232 | -| ChainSingleton | 10 | 5 | 41 | 45 | 4 | 37 | 45 | 2 | 0 | 0 | 199296 | -| ChainFactory | 10 | 5 | 43 | 50 | 8 | 35 | 50 | 2 | 0 | 0 | 199296 | -| AsyncChain | 10 | 5 | 49 | 50 | 2 | 47 | 50 | 2 | 0 | 0 | 199344 | -| Named | 10 | 5 | 1 | 1 | 0 | 1 | 1 | 2 | 0 | 0 | 199344 | -| Override | 10 | 5 | 2 | 2 | 1 | 1 | 2 | 2 | 0 | 0 | 199360 | +## Метрики + +Бенчмарки собирают: +- **Время** (мкс): среднее, медиана, stddev, min, max, полный лист замеров +- **Память (RSS):** + - memory_diff_kb — итоговая разница RSS (KB) + - delta_peak_kb — разница пикового RSS (KB) + - peak_rss_kb — абсолютный пик (KB) --- From d523a5f261b42fd85b69df0ab466205ca8923f30 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 08:28:23 +0300 Subject: [PATCH 21/32] refactor: simplify DIAdapter interface with a single registration callback; update benchmarks and cherrypick adapter accordingly --- .../universal_chain_async_benchmark.dart | 18 ++++--- .../benchmarks/universal_chain_benchmark.dart | 54 ++++++++++--------- .../lib/di_adapters/cherrypick_adapter.dart | 50 ++++++----------- .../lib/di_adapters/di_adapter.dart | 21 ++++---- 4 files changed, 67 insertions(+), 76 deletions(-) diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart index 524dadb..569878a 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart @@ -18,14 +18,16 @@ class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { @override Future setup() async { - di.setupModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: mode, - scenario: UniversalScenario.asyncChain, - ) - ]); + di.setupDependencies((scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: UniversalScenario.asyncChain, + ), + ]); + }); } @override diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart index 0818608..2d33b90 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart @@ -23,33 +23,39 @@ class UniversalChainBenchmark extends BenchmarkBase { void setup() { switch (scenario) { case UniversalScenario.override: - _di.setupModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: UniversalBindingMode.singletonStrategy, - scenario: UniversalScenario.register, - ) - ]); + _di.setupDependencies((scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.register, + ), + ]); + }); _childDi = _di.openSubScope('child'); - _childDi!.setupModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: UniversalBindingMode.singletonStrategy, - scenario: UniversalScenario.register, - ) - ]); + _childDi!.setupDependencies((scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.register, + ), + ]); + }); break; default: - _di.setupModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: mode, - scenario: scenario, - ) - ]); + _di.setupDependencies((scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: scenario, + ), + ]); + }); break; } } diff --git a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart index 55bcdfd..2b002bd 100644 --- a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart @@ -1,33 +1,23 @@ import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; -/// DIAdapter implementation for the CherryPick DI library. -/// -/// Wraps a CherryPick [Scope] and provides methods -/// to setup modules, resolve dependencies, teardown, -/// and open nested sub-scopes for benchmarking. +/// DIAdapter implementation for the CherryPick DI library using registration callbacks. class CherrypickDIAdapter implements DIAdapter { Scope? _scope; - + @override - void setupModules(List modules) { + void setupDependencies(void Function(dynamic container) registration) { _scope = CherryPick.openRootScope(); - _scope!.installModules(modules); + registration(_scope!); } @override - T resolve({String? named}) { - return named == null - ? _scope!.resolve() - : _scope!.resolve(named: named); - } + T resolve({String? named}) => + named == null ? _scope!.resolve() : _scope!.resolve(named: named); @override - Future resolveAsync({String? named}) async { - return named == null - ? await _scope!.resolveAsync() - : await _scope!.resolveAsync(named: named); - } + Future resolveAsync({String? named}) async => + named == null ? await _scope!.resolveAsync() : await _scope!.resolveAsync(named: named); @override void teardown() { @@ -42,33 +32,27 @@ class CherrypickDIAdapter implements DIAdapter { } } -/// Internal adapter for a CherryPick sub-scope. -/// Used for simulating child/override DI scopes in benchmarks. +/// Internal adapter for a CherryPick sub-scope (callbacks based). class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { final Scope _subScope; _CherrypickSubScopeAdapter(this._subScope); + @override - void setupModules(List modules) { - _subScope.installModules(modules); + void setupDependencies(void Function(dynamic container) registration) { + registration(_subScope); } @override - T resolve({String? named}) { - return named == null - ? _subScope.resolve() - : _subScope.resolve(named: named); - } + T resolve({String? named}) => + named == null ? _subScope.resolve() : _subScope.resolve(named: named); @override - Future resolveAsync({String? named}) async { - return named == null - ? await _subScope.resolveAsync() - : await _subScope.resolveAsync(named: named); - } + Future resolveAsync({String? named}) async => + named == null ? await _subScope.resolveAsync() : await _subScope.resolveAsync(named: named); @override void teardown() { - // subScope teardown убирать отдельно не требуется + // subScope teardown не требуется } @override diff --git a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart index 6a134eb..269f19b 100644 --- a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart @@ -1,22 +1,21 @@ -import 'package:cherrypick/cherrypick.dart'; - -/// Abstraction for Dependency Injection (DI) Adapter. +/// Абстракция для DI-адаптера с использованием функций регистрации. /// -/// Provides a uniform interface to setup, resolve, and teardown DI containers/modules -/// and open sub-scopes to benchmark them under different libraries. +/// Позволяет использовать любые DI-контейнеры: и модульные, и безмодульные. abstract class DIAdapter { - /// Installs the provided modules into the DI container. - void setupModules(List modules); + /// Устанавливает зависимости с помощью одной функции регистрации. + /// + /// Функция принимает выбранный DI-контейнер, задаваемый реализацией. + void setupDependencies(void Function(dynamic container) registration); - /// Resolves an instance of type [T] by optional [named] tag. + /// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется). T resolve({String? named}); - /// Asynchronously resolves an instance of type [T] by optional [named] tag. + /// Асинхронно резолвит экземпляр типа [T]. Future resolveAsync({String? named}); - /// Tears down or disposes of the DI container. + /// Уничтожает/отчищает DI-контейнер. void teardown(); - /// Opens a child DI sub-scope, useful for override/child-scope benchmarks. + /// Открывает дочерний под-scope (если применимо). DIAdapter openSubScope(String name); } From 64f33b20a79bd1b58a6076007f0b45e3533264c7 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 09:15:26 +0300 Subject: [PATCH 22/32] fix: universal benchmarks and DI registration; proper named binding; robust override support for cherrypick and get_it; improved CLI args --- .../universal_chain_async_benchmark.dart | 19 ++- .../benchmarks/universal_chain_benchmark.dart | 58 +++++----- .../lib/cli/benchmark_cli.dart | 4 +- benchmark_cherrypick/lib/cli/parser.dart | 5 + .../lib/di_adapters/cherrypick_adapter.dart | 11 +- .../lib/di_adapters/di_adapter.dart | 7 +- .../lib/di_adapters/get_it_adapter.dart | 32 +++++ .../scenarios/di_universal_registration.dart | 109 ++++++++++++++++++ .../lib/scenarios/universal_chain_module.dart | 15 ++- benchmark_cherrypick/pubspec.lock | 24 ++++ benchmark_cherrypick/pubspec.yaml | 1 + 11 files changed, 233 insertions(+), 52 deletions(-) create mode 100644 benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart create mode 100644 benchmark_cherrypick/lib/scenarios/di_universal_registration.dart diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart index 569878a..a45102d 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart @@ -2,6 +2,7 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; +import 'package:benchmark_cherrypick/scenarios/di_universal_registration.dart'; class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { final DIAdapter di; @@ -18,16 +19,14 @@ class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { @override Future setup() async { - di.setupDependencies((scope) { - scope.installModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: mode, - scenario: UniversalScenario.asyncChain, - ), - ]); - }); + di.setupDependencies(getUniversalRegistration( + di, + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: UniversalScenario.asyncChain, + )); + await di.waitForAsyncReady(); } @override diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart index 2d33b90..1068708 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart @@ -2,6 +2,7 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; +import 'package:benchmark_cherrypick/scenarios/di_universal_registration.dart'; class UniversalChainBenchmark extends BenchmarkBase { final DIAdapter _di; @@ -23,39 +24,30 @@ class UniversalChainBenchmark extends BenchmarkBase { void setup() { switch (scenario) { case UniversalScenario.override: - _di.setupDependencies((scope) { - scope.installModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: UniversalBindingMode.singletonStrategy, - scenario: UniversalScenario.register, - ), - ]); - }); + _di.setupDependencies(getUniversalRegistration( + _di, + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.chain, + )); _childDi = _di.openSubScope('child'); - _childDi!.setupDependencies((scope) { - scope.installModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: UniversalBindingMode.singletonStrategy, - scenario: UniversalScenario.register, - ), - ]); - }); + _childDi!.setupDependencies(getUniversalRegistration( + _childDi!, + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.chain, // критично: цепочку, а не просто alias! + )); break; default: - _di.setupDependencies((scope) { - scope.installModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: mode, - scenario: scenario, - ), - ]); - }); + _di.setupDependencies(getUniversalRegistration( + _di, + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: scenario, + )); break; } } @@ -70,7 +62,11 @@ class UniversalChainBenchmark extends BenchmarkBase { _di.resolve(); break; case UniversalScenario.named: - _di.resolve(named: 'impl2'); + if (_di.runtimeType.toString().contains('GetItAdapter')) { + _di.resolve(named: 'impl2'); + } else { + _di.resolve(named: 'impl2'); + } break; case UniversalScenario.chain: final serviceName = '${chainCount}_$nestingDepth'; diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_cherrypick/lib/cli/benchmark_cli.dart index 61ca734..0db499a 100644 --- a/benchmark_cherrypick/lib/cli/benchmark_cli.dart +++ b/benchmark_cherrypick/lib/cli/benchmark_cli.dart @@ -11,6 +11,7 @@ import 'runner.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; +import 'package:benchmark_cherrypick/di_adapters/get_it_adapter.dart'; /// Command-line interface (CLI) runner for benchmarks. /// @@ -28,8 +29,8 @@ class BenchmarkCliRunner { for (final c in config.chainCounts) { for (final d in config.nestDepths) { BenchmarkResult benchResult; + final di = config.di == 'getit' ? GetItAdapter() : CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { - final di = CherrypickDIAdapter(); final benchAsync = UniversalChainAsyncBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, ); @@ -39,7 +40,6 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final di = CherrypickDIAdapter(); final benchSync = UniversalChainBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); diff --git a/benchmark_cherrypick/lib/cli/parser.dart b/benchmark_cherrypick/lib/cli/parser.dart index 11ad208..73ee533 100644 --- a/benchmark_cherrypick/lib/cli/parser.dart +++ b/benchmark_cherrypick/lib/cli/parser.dart @@ -81,6 +81,8 @@ class BenchmarkCliConfig { final int warmups; /// Output report format. final String format; + /// Name of DI implementation ("cherrypick" or "getit") + final String di; BenchmarkCliConfig({ required this.benchesToRun, required this.chainCounts, @@ -88,6 +90,7 @@ class BenchmarkCliConfig { required this.repeats, required this.warmups, required this.format, + required this.di, }); } @@ -101,6 +104,7 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { ..addOption('repeat', abbr: 'r', defaultsTo: '2') ..addOption('warmup', abbr: 'w', defaultsTo: '1') ..addOption('format', abbr: 'f', defaultsTo: 'pretty') + ..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick or getit') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); final result = parser.parse(args); if (result['help'] == true) { @@ -120,5 +124,6 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { repeats: int.tryParse(result['repeat'] as String? ?? "") ?? 2, warmups: int.tryParse(result['warmup'] as String? ?? "") ?? 1, format: result['format'] as String, + di: result['di'] as String? ?? 'cherrypick', ); } \ No newline at end of file diff --git a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart index 2b002bd..fd44e2f 100644 --- a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart @@ -12,11 +12,11 @@ class CherrypickDIAdapter implements DIAdapter { } @override - T resolve({String? named}) => + T resolve({String? named}) => named == null ? _scope!.resolve() : _scope!.resolve(named: named); @override - Future resolveAsync({String? named}) async => + Future resolveAsync({String? named}) async => named == null ? await _scope!.resolveAsync() : await _scope!.resolveAsync(named: named); @override @@ -30,6 +30,9 @@ class CherrypickDIAdapter implements DIAdapter { final sub = _scope!.openSubScope(name); return _CherrypickSubScopeAdapter(sub); } + + @override + Future waitForAsyncReady() async {} } /// Internal adapter for a CherryPick sub-scope (callbacks based). @@ -43,11 +46,11 @@ class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { } @override - T resolve({String? named}) => + T resolve({String? named}) => named == null ? _subScope.resolve() : _subScope.resolve(named: named); @override - Future resolveAsync({String? named}) async => + Future resolveAsync({String? named}) async => named == null ? await _subScope.resolveAsync() : await _subScope.resolveAsync(named: named); @override diff --git a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart index 269f19b..34aebd7 100644 --- a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart +++ b/benchmark_cherrypick/lib/di_adapters/di_adapter.dart @@ -8,14 +8,17 @@ abstract class DIAdapter { void setupDependencies(void Function(dynamic container) registration); /// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется). - T resolve({String? named}); + T resolve({String? named}); /// Асинхронно резолвит экземпляр типа [T]. - Future resolveAsync({String? named}); + Future resolveAsync({String? named}); /// Уничтожает/отчищает DI-контейнер. void teardown(); /// Открывает дочерний под-scope (если применимо). DIAdapter openSubScope(String name); + + /// Ожидание готовности DI контейнера (нужно для async DI, например get_it) + Future waitForAsyncReady(); } diff --git a/benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart b/benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart new file mode 100644 index 0000000..886827e --- /dev/null +++ b/benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart @@ -0,0 +1,32 @@ +import 'package:get_it/get_it.dart'; +import 'di_adapter.dart'; + +class GetItAdapter implements DIAdapter { + late GetIt _getIt; + + @override + void setupDependencies(void Function(dynamic container) registration) { + _getIt = GetIt.asNewInstance(); + registration(_getIt); + } + + @override + T resolve({String? named}) => _getIt(instanceName: named); + + @override + Future resolveAsync({String? named}) async => _getIt(instanceName: named); + + @override + void teardown() => _getIt.reset(); + + @override + DIAdapter openSubScope(String name) { + // get_it не поддерживает scope, возвращаем новый инстанс + return GetItAdapter(); + } + + @override + Future waitForAsyncReady() async { + await _getIt.allReady(); + } +} diff --git a/benchmark_cherrypick/lib/scenarios/di_universal_registration.dart b/benchmark_cherrypick/lib/scenarios/di_universal_registration.dart new file mode 100644 index 0000000..d0351af --- /dev/null +++ b/benchmark_cherrypick/lib/scenarios/di_universal_registration.dart @@ -0,0 +1,109 @@ +import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; + +import '../di_adapters/di_adapter.dart'; +import '../di_adapters/cherrypick_adapter.dart'; +import '../di_adapters/get_it_adapter.dart'; +import 'universal_chain_module.dart'; +import 'universal_service.dart'; +import 'package:get_it/get_it.dart'; +import 'package:cherrypick/cherrypick.dart'; + +/// Возвращает универсальную функцию регистрации зависимостей, +/// подходящую под выбранный DI-адаптер. +void Function(dynamic) getUniversalRegistration( + DIAdapter adapter, { + required int chainCount, + required int nestingDepth, + required UniversalBindingMode bindingMode, + required UniversalScenario scenario, +}) { + if (adapter is CherrypickDIAdapter) { + return (scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: bindingMode, + scenario: scenario, + ), + ]); + }; + } else if (adapter is GetItAdapter) { + return (getIt) { + 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'; + getIt.registerSingletonAsync( + () async { + final prev = level > 1 + ? await getIt.getAsync(instanceName: prevDepName) + : null; + return UniversalServiceImpl(value: depName, dependency: prev); + }, + instanceName: depName, + ); + } + } + break; + case UniversalScenario.register: + getIt.registerSingleton(UniversalServiceImpl(value: 'reg', dependency: null)); + break; + case UniversalScenario.named: + getIt.registerFactory(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1'); + getIt.registerFactory(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2'); + break; + case UniversalScenario.chain: + for (int chain = 1; chain <= chainCount; chain++) { + for (int level = 1; level <= nestingDepth; level++) { + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + switch (bindingMode) { + case UniversalBindingMode.singletonStrategy: + getIt.registerSingleton( + UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? getIt(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + case UniversalBindingMode.factoryStrategy: + getIt.registerFactory( + () => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? getIt(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + case UniversalBindingMode.asyncStrategy: + // getIt не поддерживает асинх. factory напрямую, но можно так: + getIt.registerSingletonAsync( + () async => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? await getIt.getAsync(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + } + } + } + break; + case UniversalScenario.override: + // handled at benchmark level + break; + } + }; + } + throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); +} diff --git a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart index c006581..b4657c1 100644 --- a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart +++ b/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart @@ -85,8 +85,8 @@ class UniversalChainModule extends Module { break; case UniversalScenario.named: // Named factory registration for two distinct objects. - bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); - bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); + bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); + bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); break; case UniversalScenario.chain: // Chain of nested services, with dependency on previous level by name. @@ -126,9 +126,18 @@ class UniversalChainModule extends Module { } } } + // Регистрация алиаса без имени (на последний элемент цепочки) + final depName = '${chainCount}_${nestingDepth}'; + bind() + .toProvide(() => currentScope.resolve(named: depName)) + .singleton(); break; case UniversalScenario.override: - // handled at benchmark level + // handled at benchmark level, но алиас нужен прямо в этом scope! + final depName = '${chainCount}_${nestingDepth}'; + bind() + .toProvide(() => currentScope.resolve(named: depName)) + .singleton(); break; case UniversalScenario.asyncChain: // already handled above diff --git a/benchmark_cherrypick/pubspec.lock b/benchmark_cherrypick/pubspec.lock index 2b5229b..24877a8 100644 --- a/benchmark_cherrypick/pubspec.lock +++ b/benchmark_cherrypick/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" benchmark_harness: dependency: "direct dev" description: @@ -40,6 +48,14 @@ packages: relative: true source: path version: "3.0.0-dev.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" exception_templates: dependency: transitive description: @@ -48,6 +64,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.1" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b + url: "https://pub.dev" + source: hosted + version: "8.2.0" lazy_memo: dependency: transitive description: diff --git a/benchmark_cherrypick/pubspec.yaml b/benchmark_cherrypick/pubspec.yaml index c3a3074..4dc437d 100644 --- a/benchmark_cherrypick/pubspec.yaml +++ b/benchmark_cherrypick/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: cherrypick: path: ../cherrypick args: ^2.7.0 + get_it: ^8.2.0 dev_dependencies: lints: ^5.0.0 From da79f1e5468c63f7b0763134a7ed3e89a140e1f4 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 10:16:14 +0300 Subject: [PATCH 23/32] docs: update README.md and README.ru.md for universal DI benchmarks, scenarios, CLI options, and architecture diagram --- benchmark_cherrypick/README.md | 219 +++++++++++++++++++----------- benchmark_cherrypick/README.ru.md | 201 ++++++++++++++++++--------- 2 files changed, 278 insertions(+), 142 deletions(-) diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md index cdc83f0..8bad6b0 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_cherrypick/README.md @@ -1,121 +1,188 @@ # benchmark_cherrypick -_Benchmark suite for cherrypick DI container and its features._ +_Benchmark suite for cherrypick DI container, get_it, and other DI solutions._ ## Overview -This package provides comprehensive benchmarks for the [cherrypick](https://github.com/) dependency injection core and comparable DI scenarios. It includes a CLI tool for running a matrix of synthetic scenarios—covering depth and breadth, named resolutions, scope overrides, async chains, memory usage and more. +benchmark_cherrypick is a flexible benchmarking suite to compare DI containers (like cherrypick and get_it) on synthetic, deep, and real-world dependency scenarios – chains, factories, async, named, override, etc. -**Key Features:** -- Declarative matrix runs (chain count, nesting depth, scenario, repeats) -- CLI tool with flexible configuration -- Multiple report formats: pretty table, CSV, JSON, Markdown -- Memory and runtime statistics (mean, median, stddev, min, max, memory diffs) -- Built-in and extensible scenarios (singletons, factories, named, async, overrides) -- Easy to extend with your own modules/adapters +**Features:** +- Universal registration layer and modular scenario setup (works with any DI) +- Built-in support for [cherrypick](https://github.com/) and [get_it](https://pub.dev/packages/get_it) +- Clean CLI for matrix runs and output formats (Markdown, CSV, JSON, pretty) +- Reports metrics: timings, memory (RSS, peak), statistical spreads, and more +- Extendable via your own DIAdapter or benchmark scenarios --- ## Benchmark Scenarios -- **RegisterSingleton**: Registers and resolves a singleton dependency -- **ChainSingleton**: Resolves a deep chain of singleton dependencies (A→B→C...) -- **ChainFactory**: Resolves a deep chain using factory bindings (new instance each time) -- **AsyncChain**: Resolves an async dependency chain (async providers) -- **Named**: Resolves a named dependency from several implementations -- **Override**: Resolves a dependency overridden in a child scope +- **registerSingleton**: Simple singleton registration/resolution +- **chainSingleton**: Resolution of long singleton chains (A→B→C...) +- **chainFactory**: Chain resolution via factories (new instances each time) +- **asyncChain**: Async chain (with async providers) +- **named**: Named/qualified resolution (e.g. from multiple implementations) +- **override**: Resolution and override in subScopes/child adapters + +--- + +## Supported DI + +- **cherrypick** (default) +- **get_it** +- Easy to add your own DI by creating a DIAdapter + +Switch DI with the CLI option: `--di` --- ## How to Run -1. **Get dependencies:** +1. **Install dependencies:** ```shell dart pub get ``` -2. **Run all benchmarks (default single configuration, 2 warmups, 2 repeats):** + +2. **Run all benchmarks (default: all scenarios, 2 warmup, 2 repeats):** ```shell - dart run bin/main.dart + dart run bin/main.dart --benchmark=all --format=markdown ``` -3. **Show available CLI options:** +3. **For get_it:** + ```shell + dart run bin/main.dart --di=getit --benchmark=all --format=markdown + ``` + +4. **Show all CLI options:** ```shell dart run bin/main.dart --help ``` ### CLI Parameters -- `--benchmark, -b` — Benchmark scenario: - `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` (default: all) -- `--chainCount, -c` — Comma-separated chain counts, e.g. `10,100` -- `--nestingDepth, -d` — Comma-separated chain depths, e.g. `5,10` -- `--repeat, -r` — Number of measurement runs per scenario (default: 2) -- `--warmup, -w` — Warmup runs before measuring (default: 1) -- `--format, -f` — Output format: `pretty`, `csv`, `json`, `markdown` (default: pretty) -- `--help, -h` — Show usage +- `--di` — DI implementation: `cherrypick` (default) or `getit` +- `--benchmark, -b` — Scenario: `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` +- `--chainCount, -c` — Number of parallel chains (e.g. `10,100`) +- `--nestingDepth, -d` — Chain depth (e.g. `5,10`) +- `--repeat, -r` — Measurement repeats (default: 2) +- `--warmup, -w` — Warmup runs (default: 1) +- `--format, -f` — Output: `pretty`, `csv`, `json`, `markdown` +- `--help, -h` — Usage -### Examples +### Run Examples -- **Matrix run:** +- **All benchmarks for cherrypick:** ```shell - dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=5 --warmup=2 --format=markdown + dart run bin/main.dart --di=cherrypick --benchmark=all --format=markdown ``` -- **Run just the named scenario:** +- **For get_it (all scenarios):** ```shell - dart run bin/main.dart --benchmark=named --repeat=3 + dart run bin/main.dart --di=getit --benchmark=all --format=markdown ``` -### Example Output (Markdown) +- **Specify chains/depth matrix:** + ```shell + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv + ``` -``` -| Benchmark | Chain Count | Depth | Mean (us) | ... | PeakRSS(KB) | -|------------------|-------------|-------|-----------| ... |-------------| -| ChainSingleton | 10 | 5 | 2450000 | ... | 200064 | +--- + +## How to Add Your Own DI + +1. Implement a class extending `DIAdapter` (`lib/di_adapters/your_adapter.dart`) +2. Register it in CLI (see `cli/benchmark_cli.dart`) +3. Add registration logic to `di_universal_registration.dart` to build chains for your DI + +--- + +## Architecture + +```mermaid +classDiagram + class BenchmarkCliRunner { + +run(args) + } + class UniversalChainBenchmark { + +setup() + +run() + +teardown() + } + class UniversalChainAsyncBenchmark { + +setup() + +run() + +teardown() + } + class DIAdapter { + <> + +setupDependencies(cb) + +resolve(named) + +resolveAsync(named) + +teardown() + +openSubScope(name) + +waitForAsyncReady() + } + class CherrypickDIAdapter + class GetItAdapter + class UniversalChainModule { + +builder(scope) + +chainCount + +nestingDepth + +bindingMode + +scenario + } + class UniversalService { + <> + +value + +dependency + } + class UniversalServiceImpl { + +UniversalServiceImpl(value, dependency) + } + class di_universal_registration { + +getUniversalRegistration(adapter, ...) + } + class Scope + class UniversalScenario + class UniversalBindingMode + + %% Relationships + + BenchmarkCliRunner --> UniversalChainBenchmark + BenchmarkCliRunner --> UniversalChainAsyncBenchmark + + UniversalChainBenchmark *-- DIAdapter + UniversalChainAsyncBenchmark *-- DIAdapter + + DIAdapter <|.. CherrypickDIAdapter + DIAdapter <|.. GetItAdapter + + CherrypickDIAdapter ..> Scope + GetItAdapter ..> GetIt: "uses GetIt" + + DIAdapter o--> UniversalChainModule : setupDependencies + + UniversalChainModule ..> UniversalScenario + UniversalChainModule ..> UniversalBindingMode + + UniversalChainModule o-- UniversalServiceImpl : creates + UniversalService <|.. UniversalServiceImpl + UniversalServiceImpl --> UniversalService : dependency + + BenchmarkCliRunner ..> di_universal_registration : uses + di_universal_registration ..> DIAdapter + + UniversalChainBenchmark ..> di_universal_registration : uses registrar + UniversalChainAsyncBenchmark ..> di_universal_registration : uses registrar ``` --- -## Report Formats +## Metrics -- **pretty** — Tab-delimited table (human-friendly) -- **csv** — Machine-friendly, for spreadsheets/scripts -- **json** — For automation, data pipelines -- **markdown** — Markdown table for docs/wikis/issues - ---- - -## How to Add Your Own Benchmark - -1. Implement a class extending `BenchmarkBase` (sync case) or `AsyncBenchmarkBase`. -2. Configure scenario modules/services using the DI adapter interface. -3. Add scenario selection logic if needed (see bin/main.dart). -4. Optionally extend reporters or adapters for new DI libraries. - -Example minimal benchmark: -```dart -class MyBenchmark extends BenchmarkBase { - MyBenchmark() : super('My custom'); - @override void setup() { /* setup test DI modules */ } - @override void run() { /* resolve or invoke dependency chain */ } - @override void teardown() { /* cleanup if needed */ } -} -``` - -To plug in a new DI library, implement DIAdapter and register it in CLI. - ---- - -## Metrics Collected - -All benchmarks record: -- **Time** (microseconds): mean, median, stddev, min, max, timings -- **Memory**: - - memory_diff_kb — change in RSS (KB) - - delta_peak_kb — change in peak RSS (KB) - - peak_rss_kb — absolute peak RSS (KB) - ---- +Always collected: +- **Timings** (microseconds): mean, median, stddev, min, max +- **Memory**: RSS difference, peak RSS ## License diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md index 01c9b2f..b3795b2 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_cherrypick/README.ru.md @@ -1,119 +1,188 @@ # benchmark_cherrypick -_Набор бенчмарков для анализа производительности и особенностей DI-контейнера cherrypick._ +_Бенчмаркинговый набор для cherrypick, get_it и других DI-контейнеров._ -## Описание +## Общее описание -Этот пакет предоставляет комплексные синтетические бенчмарки для DI-контейнера [cherrypick](https://github.com/). CLI-интерфейс позволяет запускать сценарии с разной глубиной, шириной, вариантами разрешения (singletons, factories, named, override, async), снимая статистику по времени и памяти, генерируя отчёты в различных форматах. +benchmark_cherrypick — это современный фреймворк для измерения производительности DI-контейнеров (как cherrypick, так и get_it) на синтетических, сложных и реальных сценариях: цепочки зависимостей, factory, async, именованные биндинги, override и пр. -**Особенности:** -- Матричный запуск (chain count, nesting depth, сценарий, повторы) -- Гибкая настройка CLI -- Много форматов отчётов: таблица, CSV, JSON, Markdown -- Подсчет времени и памяти (mean, median, stddev, min, max, разница RSS/пик) -- Встроенные и легко расширяемые сценарии (singletons, factories, async, named, override) -- Механизм подключения других DI-контейнеров через адаптеры +**Возможности:** +- Универсальный слой регистрации сценариев (работает с любым DI) +- Готовая поддержка [cherrypick](https://github.com/) и [get_it](https://pub.dev/packages/get_it) +- Удобный CLI для запусков по матрице значений параметров и различных форматов вывода (Markdown, CSV, JSON, pretty) +- Сбор и вывод метрик: время, память (RSS, peak), статистика (среднее, медиана, stddev, min/max) +- Легко расширять — создавайте свой DIAdapter и новые сценарии --- ## Сценарии бенчмарков -- **RegisterSingleton**: Регистрация и разрешение singleton-зависимости -- **ChainSingleton**: Глубокая цепочка singleton-зависимостей (A→B→C...) -- **ChainFactory**: Цепочка с factory (новый объект при каждом разрешении) -- **AsyncChain**: Асинхронная цепочка зависимостей -- **Named**: Разрешение зависимости по имени среди нескольких реализаций -- **Override**: Разрешение зависимости, перекрытой в дочернем scope +- **registerSingleton**: Регистрация и резолвинг singleton +- **chainSingleton**: Разрешение длинных singleton-цепочек (A→B→C…) +- **chainFactory**: То же, но с factory (каждый раз — новый объект) +- **asyncChain**: Асинхронная цепочка (async factory/provider) +- **named**: Разрешение по имени (например, из нескольких реализаций) +- **override**: Переопределение зависимостей в subScope + +--- + +## Поддерживаемые DI-контейнеры + +- **cherrypick** (по умолчанию) +- **get_it** +- Легко добавить свой DI через DIAdapter + +Меняется одной CLI-опцией: `--di` --- ## Как запустить -1. **Установите зависимости:** +1. **Установить зависимости:** ```shell dart pub get ``` -2. **Запустите все бенчмарки (по умолчанию: одна комбинация, 2 прогрева, 2 повтора):** + +2. **Запустить все бенчмарки (по умолчанию: все сценарии, 2 прогрева, 2 замера):** ```shell - dart run bin/main.dart + dart run bin/main.dart --benchmark=all --format=markdown ``` -3. **Показать все CLI-параметры:** +3. **Для get_it:** + ```shell + dart run bin/main.dart --di=getit --benchmark=all --format=markdown + ``` + +4. **Показать все опции CLI:** ```shell dart run bin/main.dart --help ``` -### CLI-параметры +### Параметры CLI -- `--benchmark, -b` — Сценарий: - `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` (по умолчанию: all) -- `--chainCount, -c` — Длины цепочек через запятую (`10,100`) -- `--nestingDepth, -d` — Глубины цепочек через запятую (`5,10`) -- `--repeat, -r` — Повторов на сценарий (по умолчанию 2) -- `--warmup, -w` — Прогревов до замера (по умолчанию 1) -- `--format, -f` — Формат отчёта: `pretty`, `csv`, `json`, `markdown` (по умолчанию pretty) -- `--help, -h` — Показать справку +- `--di` — Какой DI использовать: `cherrypick` (по умолчанию) или `getit` +- `--benchmark, -b` — Сценарий: `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all` +- `--chainCount, -c` — Сколько параллельных цепочек (например, `10,100`) +- `--nestingDepth, -d` — Глубина цепочки (например, `5,10`) +- `--repeat, -r` — Повторов замера (по умолчанию 2) +- `--warmup, -w` — Прогревочных запусков (по умолчанию 1) +- `--format, -f` — Формат отчёта: `pretty`, `csv`, `json`, `markdown` +- `--help, -h` — Справка ### Примеры запуска -- **Матричный запуск:** +- **Все бенчмарки для cherrypick:** ```shell - dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=5 --warmup=2 --format=markdown + dart run bin/main.dart --di=cherrypick --benchmark=all --format=markdown ``` -- **Только сценарий с именованным разрешением:** +- **Для get_it (все сценарии):** ```shell - dart run bin/main.dart --benchmark=named --repeat=3 + dart run bin/main.dart --di=getit --benchmark=all --format=markdown ``` -### Пример вывода (Markdown): - -``` -| Benchmark | Chain Count | Depth | Mean (us) | ... | PeakRSS(KB) | -|------------------|-------------|-------|-----------| ... |-------------| -| ChainSingleton | 10 | 5 | 2450000 | ... | 200064 | -``` +- **Запуск по матрице параметров:** + ```shell + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv + ``` --- -## Форматы отчёта +## Как добавить свой DI -- **pretty** — табличный человекочитаемый вывод -- **csv** — удобно для Excel и анализа скриптами -- **json** — для автотестов и аналитики -- **markdown** — Markdown-таблица (в Issues/Wiki) +1. Реализуйте класс-адаптер, реализующий `DIAdapter` (`lib/di_adapters/ваш_adapter.dart`) +2. Зарегистрируйте его в CLI (`cli/benchmark_cli.dart`) +3. Дополните универсальную функцию регистрации (`di_universal_registration.dart`), чтобы строить цепочки для вашего DI --- -## Как добавить свой бенчмарк +## Архитектура -1. Создайте класс на основе `BenchmarkBase` (для sync) или `AsyncBenchmarkBase` (для async) -2. Настройте DI через адаптер, создайте нужный модуль/сценарий -3. Добавьте новый случай в bin/main.dart для CLI -4. Для поддержки других DI-контейнеров реализуйте свой DIAdapter +```mermaid +classDiagram + class BenchmarkCliRunner { + +run(args) + } + class UniversalChainBenchmark { + +setup() + +run() + +teardown() + } + class UniversalChainAsyncBenchmark { + +setup() + +run() + +teardown() + } + class DIAdapter { + <> + +setupDependencies(cb) + +resolve(named) + +resolveAsync(named) + +teardown() + +openSubScope(name) + +waitForAsyncReady() + } + class CherrypickDIAdapter + class GetItAdapter + class UniversalChainModule { + +builder(scope) + +chainCount + +nestingDepth + +bindingMode + +scenario + } + class UniversalService { + <> + +value + +dependency + } + class UniversalServiceImpl { + +UniversalServiceImpl(value, dependency) + } + class di_universal_registration { + +getUniversalRegistration(adapter, ...) + } + class Scope + class UniversalScenario + class UniversalBindingMode -Пример минимального бенчмарка: -```dart -class MyBenchmark extends BenchmarkBase { - MyBenchmark() : super('My custom'); - @override void setup() {/* настройка DI, создание цепочки */} - @override void run() {/* разрешение/запуск */} - @override void teardown() {/* очистка, если нужно */} -} + %% Relationships + + BenchmarkCliRunner --> UniversalChainBenchmark + BenchmarkCliRunner --> UniversalChainAsyncBenchmark + + UniversalChainBenchmark *-- DIAdapter + UniversalChainAsyncBenchmark *-- DIAdapter + + DIAdapter <|.. CherrypickDIAdapter + DIAdapter <|.. GetItAdapter + + CherrypickDIAdapter ..> Scope + GetItAdapter ..> GetIt: "uses GetIt" + + DIAdapter o--> UniversalChainModule : setupDependencies + + UniversalChainModule ..> UniversalScenario + UniversalChainModule ..> UniversalBindingMode + + UniversalChainModule o-- UniversalServiceImpl : creates + UniversalService <|.. UniversalServiceImpl + UniversalServiceImpl --> UniversalService : dependency + + BenchmarkCliRunner ..> di_universal_registration : uses + di_universal_registration ..> DIAdapter + + UniversalChainBenchmark ..> di_universal_registration : uses registrar + UniversalChainAsyncBenchmark ..> di_universal_registration : uses registrar ``` --- ## Метрики -Бенчмарки собирают: -- **Время** (мкс): среднее, медиана, stddev, min, max, полный лист замеров -- **Память (RSS):** - - memory_diff_kb — итоговая разница RSS (KB) - - delta_peak_kb — разница пикового RSS (KB) - - peak_rss_kb — абсолютный пик (KB) - ---- +Всегда собираются: +- **Время** (мкс): среднее, медиана, stddev, min, max +- **Память**: прирост RSS, пиковое значение RSS ## Лицензия From 6b6564f8c3f301c684c9c97ff72324b01021f21b Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 10:34:50 +0300 Subject: [PATCH 24/32] refactor: rename benchmark_cherrypick to benchmark_di, update paths, pubspec, imports, and documentation --- {benchmark_cherrypick => benchmark_di}/README.md | 4 ++-- {benchmark_cherrypick => benchmark_di}/README.ru.md | 4 ++-- .../analysis_options.yaml | 0 {benchmark_cherrypick => benchmark_di}/bin/main.dart | 2 +- .../benchmarks/universal_chain_async_benchmark.dart | 8 ++++---- .../lib/benchmarks/universal_chain_benchmark.dart | 8 ++++---- .../lib/cli/benchmark_cli.dart | 10 +++++----- .../lib/cli/parser.dart | 2 +- .../lib/cli/report/csv_report.dart | 0 .../lib/cli/report/json_report.dart | 0 .../lib/cli/report/markdown_report.dart | 0 .../lib/cli/report/pretty_report.dart | 0 .../lib/cli/report/report_generator.dart | 0 .../lib/cli/runner.dart | 4 ++-- .../lib/di_adapters/cherrypick_adapter.dart | 0 .../lib/di_adapters/di_adapter.dart | 0 .../lib/di_adapters/get_it_adapter.dart | 0 .../lib/scenarios/di_universal_registration.dart | 2 +- .../lib/scenarios/universal_chain_module.dart | 0 .../lib/scenarios/universal_service.dart | 0 .../melos_benchmark_cherrypick.iml | 0 {benchmark_cherrypick => benchmark_di}/pubspec.lock | 0 {benchmark_cherrypick => benchmark_di}/pubspec.yaml | 4 ++-- melos.yaml | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) rename {benchmark_cherrypick => benchmark_di}/README.md (95%) rename {benchmark_cherrypick => benchmark_di}/README.ru.md (93%) rename {benchmark_cherrypick => benchmark_di}/analysis_options.yaml (100%) rename {benchmark_cherrypick => benchmark_di}/bin/main.dart (58%) rename {benchmark_cherrypick => benchmark_di}/lib/benchmarks/universal_chain_async_benchmark.dart (77%) rename {benchmark_cherrypick => benchmark_di}/lib/benchmarks/universal_chain_benchmark.dart (89%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/benchmark_cli.dart (89%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/parser.dart (98%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/report/csv_report.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/report/json_report.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/report/markdown_report.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/report/pretty_report.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/report/report_generator.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/cli/runner.dart (94%) rename {benchmark_cherrypick => benchmark_di}/lib/di_adapters/cherrypick_adapter.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/di_adapters/di_adapter.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/di_adapters/get_it_adapter.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/scenarios/di_universal_registration.dart (98%) rename {benchmark_cherrypick => benchmark_di}/lib/scenarios/universal_chain_module.dart (100%) rename {benchmark_cherrypick => benchmark_di}/lib/scenarios/universal_service.dart (100%) rename {benchmark_cherrypick => benchmark_di}/melos_benchmark_cherrypick.iml (100%) rename {benchmark_cherrypick => benchmark_di}/pubspec.lock (100%) rename {benchmark_cherrypick => benchmark_di}/pubspec.yaml (70%) diff --git a/benchmark_cherrypick/README.md b/benchmark_di/README.md similarity index 95% rename from benchmark_cherrypick/README.md rename to benchmark_di/README.md index 8bad6b0..e93506c 100644 --- a/benchmark_cherrypick/README.md +++ b/benchmark_di/README.md @@ -1,10 +1,10 @@ -# benchmark_cherrypick +# benchmark_di _Benchmark suite for cherrypick DI container, get_it, and other DI solutions._ ## Overview -benchmark_cherrypick is a flexible benchmarking suite to compare DI containers (like cherrypick and get_it) on synthetic, deep, and real-world dependency scenarios – chains, factories, async, named, override, etc. +benchmark_di is a flexible benchmarking suite to compare DI containers (like cherrypick and get_it) on synthetic, deep, and real-world dependency scenarios – chains, factories, async, named, override, etc. **Features:** - Universal registration layer and modular scenario setup (works with any DI) diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_di/README.ru.md similarity index 93% rename from benchmark_cherrypick/README.ru.md rename to benchmark_di/README.ru.md index b3795b2..9e80424 100644 --- a/benchmark_cherrypick/README.ru.md +++ b/benchmark_di/README.ru.md @@ -1,10 +1,10 @@ -# benchmark_cherrypick +# benchmark_di _Бенчмаркинговый набор для cherrypick, get_it и других DI-контейнеров._ ## Общее описание -benchmark_cherrypick — это современный фреймворк для измерения производительности DI-контейнеров (как cherrypick, так и get_it) на синтетических, сложных и реальных сценариях: цепочки зависимостей, factory, async, именованные биндинги, override и пр. +benchmark_di — это современный фреймворк для измерения производительности DI-контейнеров (как cherrypick, так и get_it) на синтетических, сложных и реальных сценариях: цепочки зависимостей, factory, async, именованные биндинги, override и пр. **Возможности:** - Универсальный слой регистрации сценариев (работает с любым DI) diff --git a/benchmark_cherrypick/analysis_options.yaml b/benchmark_di/analysis_options.yaml similarity index 100% rename from benchmark_cherrypick/analysis_options.yaml rename to benchmark_di/analysis_options.yaml diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_di/bin/main.dart similarity index 58% rename from benchmark_cherrypick/bin/main.dart rename to benchmark_di/bin/main.dart index 3c51a09..985adbd 100644 --- a/benchmark_cherrypick/bin/main.dart +++ b/benchmark_di/bin/main.dart @@ -1,4 +1,4 @@ -import 'package:benchmark_cherrypick/cli/benchmark_cli.dart'; +import 'package:benchmark_di/cli/benchmark_cli.dart'; Future main(List args) async { await BenchmarkCliRunner().run(args); diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart similarity index 77% rename from benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart rename to benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart index a45102d..55b225e 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart @@ -1,8 +1,8 @@ import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; -import 'package:benchmark_cherrypick/scenarios/di_universal_registration.dart'; +import 'package:benchmark_di/di_adapters/di_adapter.dart'; +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; +import 'package:benchmark_di/scenarios/di_universal_registration.dart'; class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { final DIAdapter di; diff --git a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart similarity index 89% rename from benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart rename to benchmark_di/lib/benchmarks/universal_chain_benchmark.dart index 1068708..d814709 100644 --- a/benchmark_cherrypick/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -1,8 +1,8 @@ import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:benchmark_cherrypick/di_adapters/di_adapter.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; -import 'package:benchmark_cherrypick/scenarios/di_universal_registration.dart'; +import 'package:benchmark_di/di_adapters/di_adapter.dart'; +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; +import 'package:benchmark_di/scenarios/di_universal_registration.dart'; class UniversalChainBenchmark extends BenchmarkBase { final DIAdapter _di; diff --git a/benchmark_cherrypick/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart similarity index 89% rename from benchmark_cherrypick/lib/cli/benchmark_cli.dart rename to benchmark_di/lib/cli/benchmark_cli.dart index 0db499a..a904a81 100644 --- a/benchmark_cherrypick/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:benchmark_cherrypick/cli/report/markdown_report.dart'; +import 'package:benchmark_di/cli/report/markdown_report.dart'; import '../scenarios/universal_chain_module.dart'; import 'report/pretty_report.dart'; @@ -8,10 +8,10 @@ import 'report/csv_report.dart'; import 'report/json_report.dart'; import 'parser.dart'; import 'runner.dart'; -import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; -import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; -import 'package:benchmark_cherrypick/di_adapters/get_it_adapter.dart'; +import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; +import 'package:benchmark_di/di_adapters/cherrypick_adapter.dart'; +import 'package:benchmark_di/di_adapters/get_it_adapter.dart'; /// Command-line interface (CLI) runner for benchmarks. /// diff --git a/benchmark_cherrypick/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart similarity index 98% rename from benchmark_cherrypick/lib/cli/parser.dart rename to benchmark_di/lib/cli/parser.dart index 73ee533..27d0719 100644 --- a/benchmark_cherrypick/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:args/args.dart'; -import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; /// Enum describing all supported Universal DI benchmark types. enum UniversalBenchmark { diff --git a/benchmark_cherrypick/lib/cli/report/csv_report.dart b/benchmark_di/lib/cli/report/csv_report.dart similarity index 100% rename from benchmark_cherrypick/lib/cli/report/csv_report.dart rename to benchmark_di/lib/cli/report/csv_report.dart diff --git a/benchmark_cherrypick/lib/cli/report/json_report.dart b/benchmark_di/lib/cli/report/json_report.dart similarity index 100% rename from benchmark_cherrypick/lib/cli/report/json_report.dart rename to benchmark_di/lib/cli/report/json_report.dart diff --git a/benchmark_cherrypick/lib/cli/report/markdown_report.dart b/benchmark_di/lib/cli/report/markdown_report.dart similarity index 100% rename from benchmark_cherrypick/lib/cli/report/markdown_report.dart rename to benchmark_di/lib/cli/report/markdown_report.dart diff --git a/benchmark_cherrypick/lib/cli/report/pretty_report.dart b/benchmark_di/lib/cli/report/pretty_report.dart similarity index 100% rename from benchmark_cherrypick/lib/cli/report/pretty_report.dart rename to benchmark_di/lib/cli/report/pretty_report.dart diff --git a/benchmark_cherrypick/lib/cli/report/report_generator.dart b/benchmark_di/lib/cli/report/report_generator.dart similarity index 100% rename from benchmark_cherrypick/lib/cli/report/report_generator.dart rename to benchmark_di/lib/cli/report/report_generator.dart diff --git a/benchmark_cherrypick/lib/cli/runner.dart b/benchmark_di/lib/cli/runner.dart similarity index 94% rename from benchmark_cherrypick/lib/cli/runner.dart rename to benchmark_di/lib/cli/runner.dart index 5687dba..ae6835d 100644 --- a/benchmark_cherrypick/lib/cli/runner.dart +++ b/benchmark_di/lib/cli/runner.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'dart:math'; -import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; -import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; +import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; /// Holds the results for a single benchmark execution. class BenchmarkResult { diff --git a/benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart similarity index 100% rename from benchmark_cherrypick/lib/di_adapters/cherrypick_adapter.dart rename to benchmark_di/lib/di_adapters/cherrypick_adapter.dart diff --git a/benchmark_cherrypick/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart similarity index 100% rename from benchmark_cherrypick/lib/di_adapters/di_adapter.dart rename to benchmark_di/lib/di_adapters/di_adapter.dart diff --git a/benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart similarity index 100% rename from benchmark_cherrypick/lib/di_adapters/get_it_adapter.dart rename to benchmark_di/lib/di_adapters/get_it_adapter.dart diff --git a/benchmark_cherrypick/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart similarity index 98% rename from benchmark_cherrypick/lib/scenarios/di_universal_registration.dart rename to benchmark_di/lib/scenarios/di_universal_registration.dart index d0351af..a32d54a 100644 --- a/benchmark_cherrypick/lib/scenarios/di_universal_registration.dart +++ b/benchmark_di/lib/scenarios/di_universal_registration.dart @@ -1,4 +1,4 @@ -import 'package:benchmark_cherrypick/scenarios/universal_service.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; import '../di_adapters/di_adapter.dart'; import '../di_adapters/cherrypick_adapter.dart'; diff --git a/benchmark_cherrypick/lib/scenarios/universal_chain_module.dart b/benchmark_di/lib/scenarios/universal_chain_module.dart similarity index 100% rename from benchmark_cherrypick/lib/scenarios/universal_chain_module.dart rename to benchmark_di/lib/scenarios/universal_chain_module.dart diff --git a/benchmark_cherrypick/lib/scenarios/universal_service.dart b/benchmark_di/lib/scenarios/universal_service.dart similarity index 100% rename from benchmark_cherrypick/lib/scenarios/universal_service.dart rename to benchmark_di/lib/scenarios/universal_service.dart diff --git a/benchmark_cherrypick/melos_benchmark_cherrypick.iml b/benchmark_di/melos_benchmark_cherrypick.iml similarity index 100% rename from benchmark_cherrypick/melos_benchmark_cherrypick.iml rename to benchmark_di/melos_benchmark_cherrypick.iml diff --git a/benchmark_cherrypick/pubspec.lock b/benchmark_di/pubspec.lock similarity index 100% rename from benchmark_cherrypick/pubspec.lock rename to benchmark_di/pubspec.lock diff --git a/benchmark_cherrypick/pubspec.yaml b/benchmark_di/pubspec.yaml similarity index 70% rename from benchmark_cherrypick/pubspec.yaml rename to benchmark_di/pubspec.yaml index 4dc437d..525f529 100644 --- a/benchmark_cherrypick/pubspec.yaml +++ b/benchmark_di/pubspec.yaml @@ -1,7 +1,7 @@ -name: benchmark_cherrypick +name: benchmark_di version: 0.1.0 publish_to: none -description: Benchmark for cherrypick core DI library +description: Universal benchmark for any DI library (cherrypick, get_it, and others) environment: sdk: '>=3.0.0 <4.0.0' diff --git a/melos.yaml b/melos.yaml index 49f9048..2dc15b2 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,7 +3,7 @@ name: cherrypick_workspace sdkPath: .fvm/flutter_sdk packages: - - benchmark_cherrypick + - benchmark_di - cherrypick - cherrypick_flutter - cherrypick_annotations From f7a7ea438493befdf07134d5b2cf28c4f7319a40 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 12:11:16 +0300 Subject: [PATCH 25/32] feat: full di benchmarks report (en/ru) + get_it scope+override support fix; fresh results for all scenarios and settings --- benchmark_di/REPORT.md | 79 +++++++++++++++++++ benchmark_di/REPORT.ru.md | 79 +++++++++++++++++++ .../lib/di_adapters/get_it_adapter.dart | 48 ++++++++++- .../scenarios/di_universal_registration.dart | 9 ++- 4 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 benchmark_di/REPORT.md create mode 100644 benchmark_di/REPORT.ru.md diff --git a/benchmark_di/REPORT.md b/benchmark_di/REPORT.md new file mode 100644 index 0000000..1a8b43d --- /dev/null +++ b/benchmark_di/REPORT.md @@ -0,0 +1,79 @@ +# DI Benchmark Results: cherrypick vs get_it + +## Benchmark parameters + +| Parameter | Value | +|------------------|-----------------------| +| --benchmark | all | +| --chainCount (-c)| 10, 100 | +| --nestingDepth (-d)| 10, 100 | +| --repeat (-r) | 2 | +| --warmup (-w) | 1 (default) | +| --format (-f) | markdown | +| --di | cherrypick, get_it | + +--- + +## Benchmark scenarios + +**(1) RegisterSingleton** +Registers and resolves a singleton. Baseline DI speed. + +**(2) ChainSingleton** +A dependency chain A → B → ... → N (singleton). Measures how fast DI resolves deep singleton chains by name. + +**(3) ChainFactory** +Same as ChainSingleton, but every chain element is a factory. Shows DI speed for stateless 'creation chain'. + +**(4) AsyncChain** +Async chain (async factory). Measures DI performance for async graphs. + +**(5) Named** +Registers two bindings with names ("impl1", "impl2"), resolves by name. Tests named lookup. + +**(6) Override** +Registers a chain/alias in a child scope and resolves UniversalService without a name in that scope. Simulates override and modular/test architecture. + +--- + +## Comparative Table (Mean, ΔRSS), chainCount=10, nestingDepth=10 + +| Scenario | cherrypick Mean (us) | cherrypick ΔRSS | get_it Mean (us) | get_it ΔRSS | +|--------------------|---------------------:|----------------:|-----------------:|------------:| +| RegisterSingleton | 21.0 | 320 | 24.5 | 80 | +| ChainSingleton | 112.5 | -3008 | 2.0 | 304 | +| ChainFactory | 8.0 | 0 | 4.0 | 0 | +| AsyncChain | 36.5 | 0 | 13.5 | 0 | +| Named | 1.5 | 0 | 0.5 | 0 | +| Override | 27.5 | 0 | 0.0 | 0 | + +## Maximum load: chainCount=100, nestingDepth=100 + +| Scenario | cherrypick Mean (us) | cherrypick ΔRSS | get_it Mean (us) | get_it ΔRSS | +|--------------------|---------------------:|----------------:|-----------------:|------------:| +| RegisterSingleton | 1.0 | 32 | 1.0 | 0 | +| ChainSingleton | 3884.0 | 0 | 1.5 | 34848 | +| ChainFactory | 4088.0 | 0 | 50.0 | 12528 | +| AsyncChain | 4287.0 | 0 | 17.0 | 63120 | +| Named | 1.0 | 0 | 0.0 | 0 | +| Override | 4767.5 | 0 | 1.5 | 14976 | + +--- + +## Scenario explanations + +- **RegisterSingleton:** Registers and resolves a singleton dependency, baseline test for cold/hot startup speed. +- **ChainSingleton:** Deep chain of singleton dependencies. Cherrypick is much slower as depth increases; get_it is nearly unaffected. +- **ChainFactory:** Creation chain with new instances per resolve. get_it generally faster on large chains due to ultra-simple factory registration. +- **AsyncChain:** Async factory chain. get_it processes async resolutions much faster; cherrypick is much slower as depth increases due to async handling. +- **Named:** Both DI containers resolve named bindings nearly instantly, even on large graphs. +- **Override:** Child scope override. get_it (thanks to stack-based scopes) resolves immediately; cherrypick supports modular testing with controlled memory use. + +## Summary + +- **get_it** demonstrates impressive speed and low overhead across all scenarios and loads, but lacks diagnostics, advanced scopes, and cycle detection. +- **cherrypick** is ideal for complex, multi-layered, production or testable architectures where scope, overrides, and diagnostics are critical. Predictably slower on deep/wide graphs, but scales well and provides extra safety. + +**Recommendation:** +- Use cherrypick for enterprise, multi-feature/testable DI needs. +- Use get_it for fast games, scripts, tiny Apps, and hot demos. diff --git a/benchmark_di/REPORT.ru.md b/benchmark_di/REPORT.ru.md new file mode 100644 index 0000000..4afe303 --- /dev/null +++ b/benchmark_di/REPORT.ru.md @@ -0,0 +1,79 @@ +# Результаты бенчмарка DI: cherrypick vs get_it + +## Параметры запуска бенчмарков + +| Параметр | Значение | +|------------------|-------------------------| +| --benchmark | all | +| --chainCount (-c)| 10, 100 | +| --nestingDepth (-d)| 10, 100 | +| --repeat (-r) | 2 | +| --warmup (-w) | 1 (по умолчанию) | +| --format (-f) | markdown | +| --di | cherrypick, get_it | + +--- + +## Описание бенчмарков + +**(1) RegisterSingleton** +Регистрируется и дважды резолвится singleton. Базовый тест скорости DI. + +**(2) ChainSingleton** +Цепочка зависимостей A → B → ... → N (singleton). Тестирует скорость заполнения и разрешения глубоких singleton-цепочек по имени. + +**(3) ChainFactory** +Аналогично ChainSingleton, но каждое звено цепи — factory (новый объект при каждом resolve). + +**(4) AsyncChain** +Асинхронная цепочка (async factory). Важно для сценариев с async DI. + +**(5) Named** +Регистрируются две реализации по имени ('impl1', 'impl2'), разрешается named. Проверка lookup по имени. + +**(6) Override** +Регистрируется цепочка/alias в дочернем scope, резолвится UniversalService без имени там же. Симуляция override и изолированной/тестовой архитектуры. + +--- + +## Сравнительная таблица (Mean (us), ΔRSS(KB)), chainCount=10, nestingDepth=10 + +| Сценарий | cherrypick Mean (мкс) | cherrypick ΔRSS | get_it Mean (мкс) | get_it ΔRSS | +|-------------------|---------------------:|----------------:|-----------------:|------------:| +| RegisterSingleton | 21.0 | 320 | 24.5 | 80 | +| ChainSingleton | 112.5 | -3008 | 2.0 | 304 | +| ChainFactory | 8.0 | 0 | 4.0 | 0 | +| AsyncChain | 36.5 | 0 | 13.5 | 0 | +| Named | 1.5 | 0 | 0.5 | 0 | +| Override | 27.5 | 0 | 0.0 | 0 | + +## Максимальная нагрузка: chainCount=100, nestingDepth=100 + +| Сценарий | cherrypick Mean (мкс) | cherrypick ΔRSS | get_it Mean (мкс) | get_it ΔRSS | +|-------------------|---------------------:|----------------:|-----------------:|------------:| +| RegisterSingleton | 1.0 | 32 | 1.0 | 0 | +| ChainSingleton | 3884.0 | 0 | 1.5 | 34848 | +| ChainFactory | 4088.0 | 0 | 50.0 | 12528 | +| AsyncChain | 4287.0 | 0 | 17.0 | 63120 | +| Named | 1.0 | 0 | 0.0 | 0 | +| Override | 4767.5 | 0 | 1.5 | 14976 | + +--- + +## Пояснения по сценариям + +- **RegisterSingleton** — базовый тест DI (регистрация и резолвинг singleton). Практически мгновенно у обоих DI. +- **ChainSingleton** — глубокая singleton-цепочка. get_it вне конкуренции по скорости, cherrypick медленнее из-за более сложной логики поиска именованных зависимостей, но предсказуем. +- **ChainFactory** — цепочка Factory-объектов. cherrypick заметно медленнее на длинных цепях, get_it почти не увеличивает время. +- **AsyncChain** — асинхронная цепочка сервисов. get_it существенно быстрее, cherrypick страдает на глубине/ширине. +- **Named** — разрешение зависимостей по имени. Оба DI почти мгновенны. +- **Override** — переопределение alias без имени в дочернем scope. get_it (со стековыми scope) почти не теряет времени; cherrypick предсказуемо замедляется на глубине/ширине. + +## Итог + +- **get_it** выдаёт отличную производительность по всем сценариям, особенно на больших графах; но не поддерживает продвинутую диагностику, проверки циклов, расширенные scope. +- **cherrypick** — незаменим для работы с корпоративными/тестируемыми архитектурами и наследованием, устойчиво ведёт себя на тысячи зависимостей, но требует учёта роста времени при экстремальных нагрузках. + +**Рекомендация:** +- cherrypick — выбор для серьёзных production-систем и тестирования; +- get_it — лидер для MVP, быстрых демо, прототипов, CLI, games. diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index 886827e..e510cb5 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -21,8 +21,52 @@ class GetItAdapter implements DIAdapter { @override DIAdapter openSubScope(String name) { - // get_it не поддерживает scope, возвращаем новый инстанс - return GetItAdapter(); + // Открываем новый scope и возвращаем адаптер, который в setupDependencies будет использовать init. + return _GetItScopeAdapter(_getIt, name); + } + + @override + Future waitForAsyncReady() async { + await _getIt.allReady(); + } +} + +class _GetItScopeAdapter implements DIAdapter { + final GetIt _getIt; + final String _scopeName; + bool _scopePushed = false; + void Function(dynamic container)? _pendingRegistration; + + _GetItScopeAdapter(this._getIt, this._scopeName); + + @override + void setupDependencies(void Function(dynamic container) registration) { + _pendingRegistration = registration; + // Создаём scope через pushNewScope с init для правильной регистрации + _getIt.pushNewScope( + scopeName: _scopeName, + init: (getIt) => _pendingRegistration?.call(getIt), + ); + _scopePushed = true; + } + + @override + T resolve({String? named}) => _getIt(instanceName: named); + + @override + Future resolveAsync({String? named}) async => _getIt(instanceName: named); + + @override + void teardown() { + if (_scopePushed) { + _getIt.popScope(); + _scopePushed = false; + } + } + + @override + DIAdapter openSubScope(String name) { + return _GetItScopeAdapter(_getIt, name); } @override diff --git a/benchmark_di/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart index a32d54a..0cd2d14 100644 --- a/benchmark_di/lib/scenarios/di_universal_registration.dart +++ b/benchmark_di/lib/scenarios/di_universal_registration.dart @@ -28,7 +28,7 @@ void Function(dynamic) getUniversalRegistration( ), ]); }; - } else if (adapter is GetItAdapter) { + } else if (adapter is GetItAdapter || adapter.runtimeType.toString().contains('GetItScopeAdapter')) { return (getIt) { switch (scenario) { case UniversalScenario.asyncChain: @@ -103,6 +103,13 @@ void Function(dynamic) getUniversalRegistration( // handled at benchmark level break; } + // UniversalService alias (без имени) для chain/override-сценариев + if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { + final depName = '${chainCount}_$nestingDepth'; + getIt.registerSingleton( + getIt(instanceName: depName), + ); + } }; } throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); From 590b876cf45599b06b563d63d2cefb9aa0b1901d Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 13:12:56 +0300 Subject: [PATCH 26/32] feat: add Riverpod adapter, async-chain support via FutureProvider, full DI CLI/bench integration, benchmarking, ascii performance graphs in markdown --- benchmark_di/lib/cli/benchmark_cli.dart | 7 +- benchmark_di/lib/cli/parser.dart | 2 +- .../lib/di_adapters/riverpod_adapter.dart | 72 +++++++++++++++++++ .../scenarios/di_universal_registration.dart | 63 ++++++++++++++-- .../lib/scenarios/universal_chain_module.dart | 4 +- benchmark_di/pubspec.lock | 32 +++++++++ benchmark_di/pubspec.yaml | 1 + 7 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 benchmark_di/lib/di_adapters/riverpod_adapter.dart diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index a904a81..20dba80 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -12,6 +12,7 @@ import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_di/di_adapters/cherrypick_adapter.dart'; import 'package:benchmark_di/di_adapters/get_it_adapter.dart'; +import 'package:benchmark_di/di_adapters/riverpod_adapter.dart'; /// Command-line interface (CLI) runner for benchmarks. /// @@ -29,7 +30,11 @@ class BenchmarkCliRunner { for (final c in config.chainCounts) { for (final d in config.nestDepths) { BenchmarkResult benchResult; - final di = config.di == 'getit' ? GetItAdapter() : CherrypickDIAdapter(); + final di = config.di == 'getit' + ? GetItAdapter() + : config.di == 'riverpod' + ? RiverpodAdapter() + : CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart index 27d0719..8321bf1 100644 --- a/benchmark_di/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -104,7 +104,7 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { ..addOption('repeat', abbr: 'r', defaultsTo: '2') ..addOption('warmup', abbr: 'w', defaultsTo: '1') ..addOption('format', abbr: 'f', defaultsTo: 'pretty') - ..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick or getit') + ..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod') ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help'); final result = parser.parse(args); if (result['help'] == true) { diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart new file mode 100644 index 0000000..93e82e1 --- /dev/null +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -0,0 +1,72 @@ +import 'package:riverpod/riverpod.dart'; +import 'di_adapter.dart'; + +/// RiverpodAdapter реализует DIAdapter для универсального бенчмарка через Riverpod. +class RiverpodAdapter implements DIAdapter { + late ProviderContainer _container; + late final Map> _namedProviders; + final ProviderContainer? _parent; + + // Основной конструктор + RiverpodAdapter() : _parent = null { + _namedProviders = >{}; + } + + // Внутренний конструктор для дочерних скоупов + RiverpodAdapter._child(this._container, this._namedProviders, this._parent); + + @override + void setupDependencies(void Function(dynamic container) registration) { + // Для главного контейнера + _container = _parent == null + ? ProviderContainer() + : ProviderContainer(parent: _parent); + registration(_namedProviders); + } + + /// Регистрировать провайдеры нужно по имени-сервису. + /// Пример: container['SomeClass'] = Provider((ref) => SomeClass()); + + @override + T resolve({String? named}) { + final provider = _namedProviders[named ?? T.toString()]; + if (provider == null) { + throw Exception('Provider not found for $named'); + } + return _container.read(provider) as T; + } + + @override + Future resolveAsync({String? named}) async { + final provider = _namedProviders[named ?? T.toString()]; + if (provider == null) { + throw Exception('Provider not found for $named'); + } + // Если это FutureProvider — используем .future + if (provider.runtimeType.toString().contains('FutureProvider')) { + final result = await _container.read((provider as dynamic).future); + return result as T; + } + return resolve(named: named); + } + + @override + void teardown() { + _container.dispose(); + _namedProviders.clear(); + } + + @override + DIAdapter openSubScope(String name) { + // Создаём дочерний scope через новый контейнер с parent + final childContainer = ProviderContainer(parent: _container); + // Провайдеры будут унаследованы (immutable копия), но при желании можно их расширять в дочернем scope. + return RiverpodAdapter._child(childContainer, Map.of(_namedProviders), _container); + } + + @override + Future waitForAsyncReady() async { + // Riverpod синхронный по умолчанию. + return; + } +} diff --git a/benchmark_di/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart index 0cd2d14..e25cae8 100644 --- a/benchmark_di/lib/scenarios/di_universal_registration.dart +++ b/benchmark_di/lib/scenarios/di_universal_registration.dart @@ -4,9 +4,7 @@ import '../di_adapters/di_adapter.dart'; import '../di_adapters/cherrypick_adapter.dart'; import '../di_adapters/get_it_adapter.dart'; import 'universal_chain_module.dart'; -import 'universal_service.dart'; -import 'package:get_it/get_it.dart'; -import 'package:cherrypick/cherrypick.dart'; +import 'package:riverpod/riverpod.dart' as rp; /// Возвращает универсальную функцию регистрации зависимостей, /// подходящую под выбранный DI-адаптер. @@ -41,7 +39,7 @@ void Function(dynamic) getUniversalRegistration( final prev = level > 1 ? await getIt.getAsync(instanceName: prevDepName) : null; - return UniversalServiceImpl(value: depName, dependency: prev); + return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?); }, instanceName: depName, ); @@ -112,5 +110,62 @@ void Function(dynamic) getUniversalRegistration( } }; } + + // Riverpod + if (adapter.runtimeType.toString().contains('RiverpodAdapter')) { + // Регистрация Provider-ов по универсальному сценарию + return (providers) { + // providers это Map> + switch (scenario) { + case UniversalScenario.register: + providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); + break; + case UniversalScenario.named: + providers['impl1'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl1')); + providers['impl2'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl2')); + break; + case UniversalScenario.chain: + for (int chain = 1; chain <= chainCount; chain++) { + for (int level = 1; level <= nestingDepth; level++) { + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + providers[depName] = rp.Provider((ref) => UniversalServiceImpl( + value: depName, + dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase) : null, + )); + } + } + // Alias для последнего (универсальное имя) + final depName = '${chainCount}_$nestingDepth'; + providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); + break; + case UniversalScenario.override: + // handled at benchmark level + break; + 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'; + providers[depName] = rp.FutureProvider((ref) async { + return UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? await ref.watch(providers[prevDepName]!.future) as UniversalService? + : null, + ); + }); + } + } + // Alias для последнего (универсальное имя) + final depName = '${chainCount}_$nestingDepth'; + providers['UniversalService'] = rp.FutureProvider((ref) async { + return await ref.watch(providers[depName]!.future) as UniversalService; + }); + break; + } + }; + } + throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); } diff --git a/benchmark_di/lib/scenarios/universal_chain_module.dart b/benchmark_di/lib/scenarios/universal_chain_module.dart index b4657c1..e7e3380 100644 --- a/benchmark_di/lib/scenarios/universal_chain_module.dart +++ b/benchmark_di/lib/scenarios/universal_chain_module.dart @@ -127,14 +127,14 @@ class UniversalChainModule extends Module { } } // Регистрация алиаса без имени (на последний элемент цепочки) - final depName = '${chainCount}_${nestingDepth}'; + final depName = '${chainCount}_$nestingDepth'; bind() .toProvide(() => currentScope.resolve(named: depName)) .singleton(); break; case UniversalScenario.override: // handled at benchmark level, но алиас нужен прямо в этом scope! - final depName = '${chainCount}_${nestingDepth}'; + final depName = '${chainCount}_$nestingDepth'; bind() .toProvide(() => currentScope.resolve(named: depName)) .singleton(); diff --git a/benchmark_di/pubspec.lock b/benchmark_di/pubspec.lock index 24877a8..11216c2 100644 --- a/benchmark_di/pubspec.lock +++ b/benchmark_di/pubspec.lock @@ -96,5 +96,37 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + riverpod: + dependency: "direct main" + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: dart: ">=3.6.0 <4.0.0" diff --git a/benchmark_di/pubspec.yaml b/benchmark_di/pubspec.yaml index 525f529..87d2865 100644 --- a/benchmark_di/pubspec.yaml +++ b/benchmark_di/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: path: ../cherrypick args: ^2.7.0 get_it: ^8.2.0 + riverpod: ^2.6.1 dev_dependencies: lints: ^5.0.0 From 54446868e468d25efaf225413222950654a486cc Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 13:44:39 +0300 Subject: [PATCH 27/32] refactor: unify DIAdapter with generics, ensure type-safety & scalability in benchmark_di - Refactor DIAdapter to generic abstract class; align interfaces for Cherrypick, GetIt, Riverpod. - Remove all dynamic/object usage from dependency registration and bench scenarios. - Universal getUniversalRegistration function now fully type-safe for all DIs. - Fix teardown and lifecycle for RiverpodAdapter to prevent disposed Container errors. - Update CLI and benchmark entry points; validated all scenarios and stress modes for each DI adapter. --- benchmark_di/lib/cli/benchmark_cli.dart | 84 ++++++++++---- .../lib/di_adapters/cherrypick_adapter.dart | 54 +++------ benchmark_di/lib/di_adapters/di_adapter.dart | 23 ++-- .../lib/di_adapters/get_it_adapter.dart | 103 ++++++++---------- .../lib/di_adapters/riverpod_adapter.dart | 64 ++++++----- .../scenarios/di_universal_registration.dart | 43 ++++---- pubspec.lock | 14 +-- 7 files changed, 193 insertions(+), 192 deletions(-) diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index 20dba80..fd26052 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -30,29 +30,69 @@ class BenchmarkCliRunner { for (final c in config.chainCounts) { for (final d in config.nestDepths) { BenchmarkResult benchResult; - final di = config.di == 'getit' - ? GetItAdapter() - : config.di == 'riverpod' - ? RiverpodAdapter() - : 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, - ); + 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, + ); + } 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(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 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, + ); + } 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 timings = benchResult.timings; timings.sort(); diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index fd44e2f..d5cf865 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -1,13 +1,18 @@ import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; -/// DIAdapter implementation for the CherryPick DI library using registration callbacks. -class CherrypickDIAdapter implements DIAdapter { +/// Универсальный DIAdapter для CherryPick с поддержкой subScope без дублирования логики. +class CherrypickDIAdapter extends DIAdapter { Scope? _scope; - + final bool _isSubScope; + + CherrypickDIAdapter([Scope? scope, this._isSubScope = false]) { + _scope = scope; + } + @override - void setupDependencies(void Function(dynamic container) registration) { - _scope = CherryPick.openRootScope(); + void setupDependencies(void Function(Scope container) registration) { + _scope ??= CherryPick.openRootScope(); registration(_scope!); } @@ -21,45 +26,18 @@ class CherrypickDIAdapter implements DIAdapter { @override void teardown() { - CherryPick.closeRootScope(); - _scope = null; + if (!_isSubScope) { + CherryPick.closeRootScope(); + _scope = null; + } + // SubScope teardown не требуется } @override CherrypickDIAdapter openSubScope(String name) { - final sub = _scope!.openSubScope(name); - return _CherrypickSubScopeAdapter(sub); + return CherrypickDIAdapter(_scope!.openSubScope(name), true); } @override Future waitForAsyncReady() async {} } - -/// Internal adapter for a CherryPick sub-scope (callbacks based). -class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { - final Scope _subScope; - _CherrypickSubScopeAdapter(this._subScope); - - @override - void setupDependencies(void Function(dynamic container) registration) { - registration(_subScope); - } - - @override - T resolve({String? named}) => - named == null ? _subScope.resolve() : _subScope.resolve(named: named); - - @override - Future resolveAsync({String? named}) async => - named == null ? await _subScope.resolveAsync() : await _subScope.resolveAsync(named: named); - - @override - void teardown() { - // subScope teardown не требуется - } - - @override - CherrypickDIAdapter openSubScope(String name) { - return _CherrypickSubScopeAdapter(_subScope.openSubScope(name)); - } -} diff --git a/benchmark_di/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart index 34aebd7..3543cce 100644 --- a/benchmark_di/lib/di_adapters/di_adapter.dart +++ b/benchmark_di/lib/di_adapters/di_adapter.dart @@ -1,24 +1,21 @@ -/// Абстракция для DI-адаптера с использованием функций регистрации. -/// -/// Позволяет использовать любые DI-контейнеры: и модульные, и безмодульные. -abstract class DIAdapter { - /// Устанавливает зависимости с помощью одной функции регистрации. - /// - /// Функция принимает выбранный DI-контейнер, задаваемый реализацией. - void setupDependencies(void Function(dynamic container) registration); +/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации. +/// Теперь для каждого адаптера задаём строгий generic тип контейнера. +abstract class DIAdapter { + /// Устанавливает зависимости с помощью строго типизированного контейнера. + void setupDependencies(void Function(TContainer container) registration); /// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется). T resolve({String? named}); - /// Асинхронно резолвит экземпляр типа [T]. + /// Асинхронно резолвит экземпляр типа [T] (если нужно). Future resolveAsync({String? named}); /// Уничтожает/отчищает DI-контейнер. void teardown(); - /// Открывает дочерний под-scope (если применимо). - DIAdapter openSubScope(String name); + /// Открывает дочерний scope и возвращает новый адаптер (если поддерживается). + DIAdapter openSubScope(String name); - /// Ожидание готовности DI контейнера (нужно для async DI, например get_it) - Future waitForAsyncReady(); + /// Ожидание готовности DI контейнера (если нужно для async DI). + Future waitForAsyncReady() async {} } diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index e510cb5..68079e6 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -1,74 +1,59 @@ import 'package:get_it/get_it.dart'; import 'di_adapter.dart'; -class GetItAdapter implements DIAdapter { +/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией. +class GetItAdapter extends DIAdapter { late GetIt _getIt; - - @override - void setupDependencies(void Function(dynamic container) registration) { - _getIt = GetIt.asNewInstance(); - registration(_getIt); - } - - @override - T resolve({String? named}) => _getIt(instanceName: named); - - @override - Future resolveAsync({String? named}) async => _getIt(instanceName: named); - - @override - void teardown() => _getIt.reset(); - - @override - DIAdapter openSubScope(String name) { - // Открываем новый scope и возвращаем адаптер, который в setupDependencies будет использовать init. - return _GetItScopeAdapter(_getIt, name); - } - - @override - Future waitForAsyncReady() async { - await _getIt.allReady(); - } -} - -class _GetItScopeAdapter implements DIAdapter { - final GetIt _getIt; - final String _scopeName; + final String? _scopeName; + final bool _isSubScope; bool _scopePushed = false; - void Function(dynamic container)? _pendingRegistration; - _GetItScopeAdapter(this._getIt, this._scopeName); - - @override - void setupDependencies(void Function(dynamic container) registration) { - _pendingRegistration = registration; - // Создаём scope через pushNewScope с init для правильной регистрации - _getIt.pushNewScope( - scopeName: _scopeName, - init: (getIt) => _pendingRegistration?.call(getIt), - ); - _scopePushed = true; - } - - @override - T resolve({String? named}) => _getIt(instanceName: named); - - @override - Future resolveAsync({String? named}) async => _getIt(instanceName: named); - - @override - void teardown() { - if (_scopePushed) { - _getIt.popScope(); - _scopePushed = false; + /// Основной (root) и subScope-конструкторы. + GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false}) + : _scopeName = scopeName, + _isSubScope = isSubScope { + if (instance != null) { + _getIt = instance; } } @override - DIAdapter openSubScope(String name) { - return _GetItScopeAdapter(_getIt, name); + void setupDependencies(void Function(GetIt container) registration) { + if (_isSubScope) { + // Создаём scope через pushNewScope с init + _getIt.pushNewScope( + scopeName: _scopeName, + init: (getIt) => registration(getIt), + ); + _scopePushed = true; + } else { + _getIt = GetIt.asNewInstance(); + registration(_getIt); + } } + @override + T resolve({String? named}) => + _getIt(instanceName: named); + + @override + Future resolveAsync({String? named}) async => + _getIt(instanceName: named); + + @override + void teardown() { + if (_isSubScope && _scopePushed) { + _getIt.popScope(); + _scopePushed = false; + } else { + _getIt.reset(); + } + } + + @override + GetItAdapter openSubScope(String name) => + GetItAdapter(instance: _getIt, scopeName: name, isSubScope: true); + @override Future waitForAsyncReady() async { await _getIt.allReady(); diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart index 93e82e1..15dc4bd 100644 --- a/benchmark_di/lib/di_adapters/riverpod_adapter.dart +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -1,67 +1,71 @@ import 'package:riverpod/riverpod.dart'; import 'di_adapter.dart'; -/// RiverpodAdapter реализует DIAdapter для универсального бенчмарка через Riverpod. -class RiverpodAdapter implements DIAdapter { - late ProviderContainer _container; - late final Map> _namedProviders; +/// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией. +class RiverpodAdapter extends DIAdapter>> { + ProviderContainer? _container; + final Map> _namedProviders; final ProviderContainer? _parent; + final bool _isSubScope; - // Основной конструктор - RiverpodAdapter() : _parent = null { - _namedProviders = >{}; - } - - // Внутренний конструктор для дочерних скоупов - RiverpodAdapter._child(this._container, this._namedProviders, this._parent); + RiverpodAdapter({ + ProviderContainer? container, + Map>? providers, + ProviderContainer? parent, + bool isSubScope = false, + }) : _container = container, + _namedProviders = providers ?? >{}, + _parent = parent, + _isSubScope = isSubScope; @override - void setupDependencies(void Function(dynamic container) registration) { - // Для главного контейнера - _container = _parent == null + void setupDependencies(void Function(Map> container) registration) { + _container ??= _parent == null ? ProviderContainer() : ProviderContainer(parent: _parent); registration(_namedProviders); } - /// Регистрировать провайдеры нужно по имени-сервису. - /// Пример: container['SomeClass'] = Provider((ref) => SomeClass()); - @override T resolve({String? named}) { - final provider = _namedProviders[named ?? T.toString()]; + final key = named ?? T.toString(); + final provider = _namedProviders[key]; if (provider == null) { - throw Exception('Provider not found for $named'); + throw Exception('Provider not found for $key'); } - return _container.read(provider) as T; + return _container!.read(provider) as T; } @override Future resolveAsync({String? named}) async { - final provider = _namedProviders[named ?? T.toString()]; + final key = named ?? T.toString(); + final provider = _namedProviders[key]; if (provider == null) { - throw Exception('Provider not found for $named'); + throw Exception('Provider not found for $key'); } // Если это FutureProvider — используем .future if (provider.runtimeType.toString().contains('FutureProvider')) { - final result = await _container.read((provider as dynamic).future); - return result as T; + return await _container!.read((provider as dynamic).future) as T; } return resolve(named: named); } @override void teardown() { - _container.dispose(); + _container?.dispose(); + _container = null; _namedProviders.clear(); } @override - DIAdapter openSubScope(String name) { - // Создаём дочерний scope через новый контейнер с parent - final childContainer = ProviderContainer(parent: _container); - // Провайдеры будут унаследованы (immutable копия), но при желании можно их расширять в дочернем scope. - return RiverpodAdapter._child(childContainer, Map.of(_namedProviders), _container); + RiverpodAdapter openSubScope(String name) { + final newContainer = ProviderContainer(parent: _container); + return RiverpodAdapter( + container: newContainer, + providers: Map.of(_namedProviders), + parent: _container, + isSubScope: true, + ); } @override diff --git a/benchmark_di/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart index e25cae8..22d7666 100644 --- a/benchmark_di/lib/scenarios/di_universal_registration.dart +++ b/benchmark_di/lib/scenarios/di_universal_registration.dart @@ -6,10 +6,12 @@ import '../di_adapters/get_it_adapter.dart'; import 'universal_chain_module.dart'; import 'package:riverpod/riverpod.dart' as rp; -/// Возвращает универсальную функцию регистрации зависимостей, -/// подходящую под выбранный DI-адаптер. -void Function(dynamic) getUniversalRegistration( - DIAdapter adapter, { +/// Унифицированный generic-колбэк для регистрации зависимостей, +/// подходящий под выбранный DI-адаптер. +typedef Registration = void Function(TContainer); + +Registration getUniversalRegistration( + DIAdapter adapter, { required int chainCount, required int nestingDepth, required UniversalBindingMode bindingMode, @@ -25,8 +27,9 @@ void Function(dynamic) getUniversalRegistration( scenario: scenario, ), ]); - }; - } else if (adapter is GetItAdapter || adapter.runtimeType.toString().contains('GetItScopeAdapter')) { + } as Registration; + } + if (adapter is GetItAdapter) { return (getIt) { switch (scenario) { case UniversalScenario.asyncChain: @@ -39,7 +42,7 @@ void Function(dynamic) getUniversalRegistration( final prev = level > 1 ? await getIt.getAsync(instanceName: prevDepName) : null; - return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?); + return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?); }, instanceName: depName, ); @@ -64,8 +67,8 @@ void Function(dynamic) getUniversalRegistration( UniversalServiceImpl( value: depName, dependency: level > 1 - ? getIt(instanceName: prevDepName) - : null, + ? getIt(instanceName: prevDepName) + : null, ), instanceName: depName, ); @@ -75,20 +78,19 @@ void Function(dynamic) getUniversalRegistration( () => UniversalServiceImpl( value: depName, dependency: level > 1 - ? getIt(instanceName: prevDepName) - : null, + ? getIt(instanceName: prevDepName) + : null, ), instanceName: depName, ); break; case UniversalBindingMode.asyncStrategy: - // getIt не поддерживает асинх. factory напрямую, но можно так: getIt.registerSingletonAsync( () async => UniversalServiceImpl( value: depName, dependency: level > 1 - ? await getIt.getAsync(instanceName: prevDepName) - : null, + ? await getIt.getAsync(instanceName: prevDepName) + : null, ), instanceName: depName, ); @@ -108,14 +110,11 @@ void Function(dynamic) getUniversalRegistration( getIt(instanceName: depName), ); } - }; + } as Registration; } - // Riverpod - if (adapter.runtimeType.toString().contains('RiverpodAdapter')) { - // Регистрация Provider-ов по универсальному сценарию + if (adapter is DIAdapter>> && adapter.runtimeType.toString().contains('RiverpodAdapter')) { return (providers) { - // providers это Map> switch (scenario) { case UniversalScenario.register: providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); @@ -135,7 +134,6 @@ void Function(dynamic) getUniversalRegistration( )); } } - // Alias для последнего (универсальное имя) final depName = '${chainCount}_$nestingDepth'; providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); break; @@ -157,15 +155,14 @@ void Function(dynamic) getUniversalRegistration( }); } } - // Alias для последнего (универсальное имя) final depName = '${chainCount}_$nestingDepth'; providers['UniversalService'] = rp.FutureProvider((ref) async { return await ref.watch(providers[depName]!.future) as UniversalService; }); break; } - }; + } as Registration; } - throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); + throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); } diff --git a/pubspec.lock b/pubspec.lock index 89c1b0a..eb70210 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "73.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.8.0" ansi_styles: dependency: transitive description: @@ -298,10 +298,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" matcher: dependency: transitive description: From 56bdb3946e63c7026e72b0b1f3210bdb4d2c8fe8 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 14:11:29 +0300 Subject: [PATCH 28/32] refactor: full generic DIAdapter workflow & universalRegistration abstraction - Made UniversalChainBenchmark and UniversalChainAsyncBenchmark fully generic with strong typing for DIAdapter - Moved all universalRegistration logic inside adapters; removed global function and typedef - Replaced dynamic/object-based registration with type-safe generic contracts end-to-end - Updated CLI and usage: all DI and scenarios are invoked via type-safe generics - Fixed all analysis errors, Riverpod async/future usages, and imports for full Dart 3 compatibility - All benchmarks fully pass for Cherrypick, GetIt, and Riverpod adapters --- .../universal_chain_async_benchmark.dart | 8 +- .../benchmarks/universal_chain_benchmark.dart | 18 +- benchmark_di/lib/cli/benchmark_cli.dart | 15 +- .../lib/di_adapters/cherrypick_adapter.dart | 24 ++- benchmark_di/lib/di_adapters/di_adapter.dart | 12 ++ .../lib/di_adapters/get_it_adapter.dart | 94 ++++++++++ .../lib/di_adapters/riverpod_adapter.dart | 88 +++++++-- .../scenarios/di_universal_registration.dart | 168 ------------------ 8 files changed, 223 insertions(+), 204 deletions(-) delete mode 100644 benchmark_di/lib/scenarios/di_universal_registration.dart diff --git a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart index 55b225e..0e7412f 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart @@ -2,10 +2,9 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_di/di_adapters/di_adapter.dart'; import 'package:benchmark_di/scenarios/universal_chain_module.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; -import 'package:benchmark_di/scenarios/di_universal_registration.dart'; -class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { - final DIAdapter di; +class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { + final DIAdapter di; final int chainCount; final int nestingDepth; final UniversalBindingMode mode; @@ -19,8 +18,7 @@ class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { @override Future setup() async { - di.setupDependencies(getUniversalRegistration( - di, + di.setupDependencies(di.universalRegistration( chainCount: chainCount, nestingDepth: nestingDepth, bindingMode: mode, diff --git a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart index d814709..b777bf0 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -2,15 +2,14 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_di/di_adapters/di_adapter.dart'; import 'package:benchmark_di/scenarios/universal_chain_module.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; -import 'package:benchmark_di/scenarios/di_universal_registration.dart'; -class UniversalChainBenchmark extends BenchmarkBase { - final DIAdapter _di; +class UniversalChainBenchmark extends BenchmarkBase { + final DIAdapter _di; final int chainCount; final int nestingDepth; final UniversalBindingMode mode; final UniversalScenario scenario; - DIAdapter? _childDi; + DIAdapter? _childDi; UniversalChainBenchmark( this._di, { @@ -24,25 +23,22 @@ class UniversalChainBenchmark extends BenchmarkBase { void setup() { switch (scenario) { case UniversalScenario.override: - _di.setupDependencies(getUniversalRegistration( - _di, + _di.setupDependencies(_di.universalRegistration( chainCount: chainCount, nestingDepth: nestingDepth, bindingMode: UniversalBindingMode.singletonStrategy, scenario: UniversalScenario.chain, )); _childDi = _di.openSubScope('child'); - _childDi!.setupDependencies(getUniversalRegistration( - _childDi!, + _childDi!.setupDependencies(_childDi!.universalRegistration( chainCount: chainCount, nestingDepth: nestingDepth, bindingMode: UniversalBindingMode.singletonStrategy, - scenario: UniversalScenario.chain, // критично: цепочку, а не просто alias! + scenario: UniversalScenario.chain, )); break; default: - _di.setupDependencies(getUniversalRegistration( - _di, + _di.setupDependencies(_di.universalRegistration( chainCount: chainCount, nestingDepth: nestingDepth, bindingMode: mode, diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index fd26052..4c1ae35 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -1,6 +1,9 @@ import 'dart:math'; import 'package:benchmark_di/cli/report/markdown_report.dart'; +import 'package:cherrypick/cherrypick.dart'; +import 'package:get_it/get_it.dart'; +import 'package:riverpod/riverpod.dart' as rp; import '../scenarios/universal_chain_module.dart'; import 'report/pretty_report.dart'; @@ -33,7 +36,7 @@ class BenchmarkCliRunner { if (config.di == 'getit') { final di = GetItAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark(di, + final benchAsync = UniversalChainAsyncBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( @@ -42,7 +45,7 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark(di, + final benchSync = UniversalChainBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( @@ -54,7 +57,7 @@ class BenchmarkCliRunner { } else if (config.di == 'riverpod') { final di = RiverpodAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark(di, + final benchAsync = UniversalChainAsyncBenchmark>>(di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( @@ -63,7 +66,7 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark(di, + final benchSync = UniversalChainBenchmark>>(di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( @@ -75,7 +78,7 @@ class BenchmarkCliRunner { } else { final di = CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { - final benchAsync = UniversalChainAsyncBenchmark(di, + final benchAsync = UniversalChainAsyncBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, ); benchResult = await BenchmarkRunner.runAsync( @@ -84,7 +87,7 @@ class BenchmarkCliRunner { repeats: config.repeats, ); } else { - final benchSync = UniversalChainBenchmark(di, + final benchSync = UniversalChainBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, scenario: scenario, ); benchResult = await BenchmarkRunner.runSync( diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index d5cf865..756e1ed 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -1,7 +1,7 @@ import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; +import '../scenarios/universal_chain_module.dart'; -/// Универсальный DIAdapter для CherryPick с поддержкой subScope без дублирования логики. class CherrypickDIAdapter extends DIAdapter { Scope? _scope; final bool _isSubScope; @@ -16,6 +16,28 @@ class CherrypickDIAdapter extends DIAdapter { registration(_scope!); } + @override + Registration universalRegistration({ + required S scenario, + required int chainCount, + required int nestingDepth, + required UniversalBindingMode bindingMode, + }) { + if (scenario is UniversalScenario) { + return (scope) { + scope.installModules([ + UniversalChainModule( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: bindingMode, + scenario: scenario, + ), + ]); + }; + } + throw UnsupportedError('Scenario $scenario not supported by CherrypickDIAdapter'); + } + @override T resolve({String? named}) => named == null ? _scope!.resolve() : _scope!.resolve(named: named); diff --git a/benchmark_di/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart index 3543cce..1ccfadb 100644 --- a/benchmark_di/lib/di_adapters/di_adapter.dart +++ b/benchmark_di/lib/di_adapters/di_adapter.dart @@ -1,9 +1,21 @@ +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; + /// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации. /// Теперь для каждого адаптера задаём строгий generic тип контейнера. +typedef Registration = void Function(TContainer); + abstract class DIAdapter { /// Устанавливает зависимости с помощью строго типизированного контейнера. void setupDependencies(void Function(TContainer container) registration); + /// Возвращает типобезопасную функцию регистрации зависимостей под конкретный сценарий. + Registration universalRegistration({ + required S scenario, + required int chainCount, + required int nestingDepth, + required UniversalBindingMode bindingMode, + }); + /// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется). T resolve({String? named}); diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index 68079e6..051cdc5 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -1,3 +1,5 @@ +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:get_it/get_it.dart'; import 'di_adapter.dart'; @@ -58,4 +60,96 @@ class GetItAdapter extends DIAdapter { Future waitForAsyncReady() async { await _getIt.allReady(); } + + @override + Registration universalRegistration({ + required S scenario, + required int chainCount, + required int nestingDepth, + required UniversalBindingMode bindingMode, + }) { + if (scenario is UniversalScenario) { + return (getIt) { + 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'; + getIt.registerSingletonAsync( + () async { + final prev = level > 1 + ? await getIt.getAsync(instanceName: prevDepName) + : null; + return UniversalServiceImpl(value: depName, dependency: prev); + }, + instanceName: depName, + ); + } + } + break; + case UniversalScenario.register: + getIt.registerSingleton(UniversalServiceImpl(value: 'reg', dependency: null)); + break; + case UniversalScenario.named: + getIt.registerFactory(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1'); + getIt.registerFactory(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2'); + break; + case UniversalScenario.chain: + for (int chain = 1; chain <= chainCount; chain++) { + for (int level = 1; level <= nestingDepth; level++) { + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + switch (bindingMode) { + case UniversalBindingMode.singletonStrategy: + getIt.registerSingleton( + UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? getIt(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + case UniversalBindingMode.factoryStrategy: + getIt.registerFactory( + () => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? getIt(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + case UniversalBindingMode.asyncStrategy: + getIt.registerSingletonAsync( + () async => UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? await getIt.getAsync(instanceName: prevDepName) + : null, + ), + instanceName: depName, + ); + break; + } + } + } + break; + case UniversalScenario.override: + // handled at benchmark level + break; + } + if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { + final depName = '${chainCount}_$nestingDepth'; + getIt.registerSingleton( + getIt(instanceName: depName), + ); + } + }; + } + throw UnsupportedError('Scenario $scenario not supported by GetItAdapter'); + } } diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart index 15dc4bd..2788828 100644 --- a/benchmark_di/lib/di_adapters/riverpod_adapter.dart +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -1,28 +1,30 @@ -import 'package:riverpod/riverpod.dart'; +import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; +import 'package:riverpod/riverpod.dart' as rp; import 'di_adapter.dart'; /// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией. -class RiverpodAdapter extends DIAdapter>> { - ProviderContainer? _container; - final Map> _namedProviders; - final ProviderContainer? _parent; +class RiverpodAdapter extends DIAdapter>> { + rp.ProviderContainer? _container; + final Map> _namedProviders; + final rp.ProviderContainer? _parent; final bool _isSubScope; RiverpodAdapter({ - ProviderContainer? container, - Map>? providers, - ProviderContainer? parent, + rp.ProviderContainer? container, + Map>? providers, + rp.ProviderContainer? parent, bool isSubScope = false, }) : _container = container, - _namedProviders = providers ?? >{}, + _namedProviders = providers ?? >{}, _parent = parent, _isSubScope = isSubScope; @override - void setupDependencies(void Function(Map> container) registration) { + void setupDependencies(void Function(Map> container) registration) { _container ??= _parent == null - ? ProviderContainer() - : ProviderContainer(parent: _parent); + ? rp.ProviderContainer() + : rp.ProviderContainer(parent: _parent); registration(_namedProviders); } @@ -59,7 +61,7 @@ class RiverpodAdapter extends DIAdapter>> { @override RiverpodAdapter openSubScope(String name) { - final newContainer = ProviderContainer(parent: _container); + final newContainer = rp.ProviderContainer(parent: _container); return RiverpodAdapter( container: newContainer, providers: Map.of(_namedProviders), @@ -73,4 +75,64 @@ class RiverpodAdapter extends DIAdapter>> { // Riverpod синхронный по умолчанию. return; } + + @override + Registration>> universalRegistration({ + required S scenario, + required int chainCount, + required int nestingDepth, + required UniversalBindingMode bindingMode, + }) { + if (scenario is UniversalScenario) { + return (providers) { + switch (scenario) { + case UniversalScenario.register: + providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); + break; + case UniversalScenario.named: + providers['impl1'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl1')); + providers['impl2'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl2')); + break; + case UniversalScenario.chain: + for (int chain = 1; chain <= chainCount; chain++) { + for (int level = 1; level <= nestingDepth; level++) { + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + providers[depName] = rp.Provider((ref) => UniversalServiceImpl( + value: depName, + dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase) : null, + )); + } + } + final depName = '${chainCount}_$nestingDepth'; + providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); + break; + case UniversalScenario.override: + // handled at benchmark level + break; + 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'; + providers[depName] = rp.FutureProvider((ref) async { + return UniversalServiceImpl( + value: depName, + dependency: level > 1 + ? await ref.watch((providers[prevDepName] as rp.FutureProvider).future) as UniversalService? + : null, + ); + }); + } + } + final depName = '${chainCount}_$nestingDepth'; + providers['UniversalService'] = rp.FutureProvider((ref) async { + return await ref.watch((providers[depName] as rp.FutureProvider).future); + }); + break; + } + }; + } + throw UnsupportedError('Scenario $scenario not supported by RiverpodAdapter'); + } } diff --git a/benchmark_di/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart deleted file mode 100644 index 22d7666..0000000 --- a/benchmark_di/lib/scenarios/di_universal_registration.dart +++ /dev/null @@ -1,168 +0,0 @@ -import 'package:benchmark_di/scenarios/universal_service.dart'; - -import '../di_adapters/di_adapter.dart'; -import '../di_adapters/cherrypick_adapter.dart'; -import '../di_adapters/get_it_adapter.dart'; -import 'universal_chain_module.dart'; -import 'package:riverpod/riverpod.dart' as rp; - -/// Унифицированный generic-колбэк для регистрации зависимостей, -/// подходящий под выбранный DI-адаптер. -typedef Registration = void Function(TContainer); - -Registration getUniversalRegistration( - DIAdapter adapter, { - required int chainCount, - required int nestingDepth, - required UniversalBindingMode bindingMode, - required UniversalScenario scenario, -}) { - if (adapter is CherrypickDIAdapter) { - return (scope) { - scope.installModules([ - UniversalChainModule( - chainCount: chainCount, - nestingDepth: nestingDepth, - bindingMode: bindingMode, - scenario: scenario, - ), - ]); - } as Registration; - } - if (adapter is GetItAdapter) { - return (getIt) { - 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'; - getIt.registerSingletonAsync( - () async { - final prev = level > 1 - ? await getIt.getAsync(instanceName: prevDepName) - : null; - return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?); - }, - instanceName: depName, - ); - } - } - break; - case UniversalScenario.register: - getIt.registerSingleton(UniversalServiceImpl(value: 'reg', dependency: null)); - break; - case UniversalScenario.named: - getIt.registerFactory(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1'); - getIt.registerFactory(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2'); - break; - case UniversalScenario.chain: - for (int chain = 1; chain <= chainCount; chain++) { - for (int level = 1; level <= nestingDepth; level++) { - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - switch (bindingMode) { - case UniversalBindingMode.singletonStrategy: - getIt.registerSingleton( - UniversalServiceImpl( - value: depName, - dependency: level > 1 - ? getIt(instanceName: prevDepName) - : null, - ), - instanceName: depName, - ); - break; - case UniversalBindingMode.factoryStrategy: - getIt.registerFactory( - () => UniversalServiceImpl( - value: depName, - dependency: level > 1 - ? getIt(instanceName: prevDepName) - : null, - ), - instanceName: depName, - ); - break; - case UniversalBindingMode.asyncStrategy: - getIt.registerSingletonAsync( - () async => UniversalServiceImpl( - value: depName, - dependency: level > 1 - ? await getIt.getAsync(instanceName: prevDepName) - : null, - ), - instanceName: depName, - ); - break; - } - } - } - break; - case UniversalScenario.override: - // handled at benchmark level - break; - } - // UniversalService alias (без имени) для chain/override-сценариев - if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) { - final depName = '${chainCount}_$nestingDepth'; - getIt.registerSingleton( - getIt(instanceName: depName), - ); - } - } as Registration; - } - - if (adapter is DIAdapter>> && adapter.runtimeType.toString().contains('RiverpodAdapter')) { - return (providers) { - switch (scenario) { - case UniversalScenario.register: - providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); - break; - case UniversalScenario.named: - providers['impl1'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl1')); - providers['impl2'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl2')); - break; - case UniversalScenario.chain: - for (int chain = 1; chain <= chainCount; chain++) { - for (int level = 1; level <= nestingDepth; level++) { - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - providers[depName] = rp.Provider((ref) => UniversalServiceImpl( - value: depName, - dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase) : null, - )); - } - } - final depName = '${chainCount}_$nestingDepth'; - providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); - break; - case UniversalScenario.override: - // handled at benchmark level - break; - 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'; - providers[depName] = rp.FutureProvider((ref) async { - return UniversalServiceImpl( - value: depName, - dependency: level > 1 - ? await ref.watch(providers[prevDepName]!.future) as UniversalService? - : null, - ); - }); - } - } - final depName = '${chainCount}_$nestingDepth'; - providers['UniversalService'] = rp.FutureProvider((ref) async { - return await ref.watch(providers[depName]!.future) as UniversalService; - }); - break; - } - } as Registration; - } - - throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); -} From 5336c225508952132cc3449346d24d08ea990407 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 14:18:16 +0300 Subject: [PATCH 29/32] test: validate all benchmark scenarios and stress runs for all DI adapters - Successfully executed all scenarios (register, chain, asyncChain, named, override, etc) for Cherrypick, GetIt, and Riverpod - Verified correct integration of fully generic DIAdapter & universalRegistration architecture - Ensured type-safety in all setup/registration flows after complete refactor - All benchmarks run and pass under stress/load params for each DI adapter --- .../universal_chain_async_benchmark.dart | 3 +- .../benchmarks/universal_chain_benchmark.dart | 3 +- benchmark_di/lib/cli/benchmark_cli.dart | 2 +- benchmark_di/lib/cli/parser.dart | 3 +- .../lib/di_adapters/cherrypick_adapter.dart | 125 ++++++++++++++- benchmark_di/lib/di_adapters/di_adapter.dart | 3 +- .../lib/di_adapters/get_it_adapter.dart | 3 +- .../lib/di_adapters/riverpod_adapter.dart | 3 +- .../lib/scenarios/universal_binding_mode.dart | 11 ++ .../lib/scenarios/universal_chain_module.dart | 147 ------------------ .../lib/scenarios/universal_scenario.dart | 13 ++ 11 files changed, 160 insertions(+), 156 deletions(-) create mode 100644 benchmark_di/lib/scenarios/universal_binding_mode.dart delete mode 100644 benchmark_di/lib/scenarios/universal_chain_module.dart create mode 100644 benchmark_di/lib/scenarios/universal_scenario.dart diff --git a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart index 0e7412f..eea5849 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart @@ -1,6 +1,7 @@ +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_di/di_adapters/di_adapter.dart'; -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; class UniversalChainAsyncBenchmark extends AsyncBenchmarkBase { diff --git a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart index b777bf0..b7eb19f 100644 --- a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -1,6 +1,7 @@ +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:benchmark_di/di_adapters/di_adapter.dart'; -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; class UniversalChainBenchmark extends BenchmarkBase { diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index 4c1ae35..1680f9d 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -1,11 +1,11 @@ import 'dart:math'; import 'package:benchmark_di/cli/report/markdown_report.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:cherrypick/cherrypick.dart'; import 'package:get_it/get_it.dart'; import 'package:riverpod/riverpod.dart' as rp; -import '../scenarios/universal_chain_module.dart'; import 'report/pretty_report.dart'; import 'report/csv_report.dart'; import 'report/json_report.dart'; diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart index 8321bf1..3c93ede 100644 --- a/benchmark_di/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -1,7 +1,8 @@ import 'dart:io'; import 'package:args/args.dart'; -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; /// Enum describing all supported Universal DI benchmark types. enum UniversalBenchmark { diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index 756e1ed..4cc4330 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -1,6 +1,129 @@ +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; +import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:cherrypick/cherrypick.dart'; import 'di_adapter.dart'; -import '../scenarios/universal_chain_module.dart'; + + +/// Test module that generates a chain of service bindings for benchmarking. +/// +/// Configurable by chain count, nesting depth, binding mode, and scenario +/// to support various DI performance tests (singleton, factory, async, etc). +class UniversalChainModule extends Module { + /// Number of chains to create. + final int chainCount; + /// Depth of each chain. + final int nestingDepth; + /// How modules are registered (factory/singleton/async). + final UniversalBindingMode bindingMode; + /// Which di scenario to generate (chained, named, etc). + final UniversalScenario scenario; + + /// Constructs a configured test DI module for the benchmarks. + UniversalChainModule({ + required this.chainCount, + required this.nestingDepth, + this.bindingMode = UniversalBindingMode.singletonStrategy, + this.scenario = UniversalScenario.chain, + }); + + @override + void builder(Scope currentScope) { + if (scenario == UniversalScenario.asyncChain) { + // Generate async chain with singleton async bindings. + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + bind() + .toProvideAsync(() async { + final prev = level > 1 + ? await currentScope.resolveAsync(named: prevDepName) + : null; + return UniversalServiceImpl( + value: depName, + dependency: prev, + ); + }) + .withName(depName) + .singleton(); + } + } + return; + } + + switch (scenario) { + case UniversalScenario.register: + // Simple singleton registration. + bind() + .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) + .singleton(); + break; + case UniversalScenario.named: + // Named factory registration for two distinct objects. + bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); + bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); + break; + case UniversalScenario.chain: + // Chain of nested services, with dependency on previous level by name. + for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { + for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { + final chain = chainIndex + 1; + final level = levelIndex + 1; + final prevDepName = '${chain}_${level - 1}'; + final depName = '${chain}_$level'; + switch (bindingMode) { + case UniversalBindingMode.singletonStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + case UniversalBindingMode.factoryStrategy: + bind() + .toProvide(() => UniversalServiceImpl( + value: depName, + dependency: currentScope.tryResolve(named: prevDepName), + )) + .withName(depName); + break; + case UniversalBindingMode.asyncStrategy: + bind() + .toProvideAsync(() async => UniversalServiceImpl( + value: depName, + dependency: await currentScope.resolveAsync(named: prevDepName), + )) + .withName(depName) + .singleton(); + break; + } + } + } + // Регистрация алиаса без имени (на последний элемент цепочки) + final depName = '${chainCount}_$nestingDepth'; + bind() + .toProvide(() => currentScope.resolve(named: depName)) + .singleton(); + break; + case UniversalScenario.override: + // handled at benchmark level, но алиас нужен прямо в этом scope! + final depName = '${chainCount}_$nestingDepth'; + bind() + .toProvide(() => currentScope.resolve(named: depName)) + .singleton(); + break; + case UniversalScenario.asyncChain: + // already handled above + break; + } + } +} + class CherrypickDIAdapter extends DIAdapter { Scope? _scope; diff --git a/benchmark_di/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart index 1ccfadb..938d8f9 100644 --- a/benchmark_di/lib/di_adapters/di_adapter.dart +++ b/benchmark_di/lib/di_adapters/di_adapter.dart @@ -1,5 +1,4 @@ -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; - +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; /// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации. /// Теперь для каждого адаптера задаём строгий generic тип контейнера. typedef Registration = void Function(TContainer); diff --git a/benchmark_di/lib/di_adapters/get_it_adapter.dart b/benchmark_di/lib/di_adapters/get_it_adapter.dart index 051cdc5..0396161 100644 --- a/benchmark_di/lib/di_adapters/get_it_adapter.dart +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -1,4 +1,5 @@ -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:get_it/get_it.dart'; import 'di_adapter.dart'; diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart index 2788828..c16452e 100644 --- a/benchmark_di/lib/di_adapters/riverpod_adapter.dart +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -1,4 +1,5 @@ -import 'package:benchmark_di/scenarios/universal_chain_module.dart'; +import 'package:benchmark_di/scenarios/universal_binding_mode.dart'; +import 'package:benchmark_di/scenarios/universal_scenario.dart'; import 'package:benchmark_di/scenarios/universal_service.dart'; import 'package:riverpod/riverpod.dart' as rp; import 'di_adapter.dart'; diff --git a/benchmark_di/lib/scenarios/universal_binding_mode.dart b/benchmark_di/lib/scenarios/universal_binding_mode.dart new file mode 100644 index 0000000..4907089 --- /dev/null +++ b/benchmark_di/lib/scenarios/universal_binding_mode.dart @@ -0,0 +1,11 @@ +/// Enum to represent the DI registration/binding mode. +enum UniversalBindingMode { + /// Singleton/provider binding. + singletonStrategy, + + /// Factory-based binding. + factoryStrategy, + + /// Async-based binding. + asyncStrategy, +} diff --git a/benchmark_di/lib/scenarios/universal_chain_module.dart b/benchmark_di/lib/scenarios/universal_chain_module.dart deleted file mode 100644 index e7e3380..0000000 --- a/benchmark_di/lib/scenarios/universal_chain_module.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:cherrypick/cherrypick.dart'; -import 'universal_service.dart'; - -/// Enum to represent the DI registration/binding mode. -enum UniversalBindingMode { - /// Singleton/provider binding. - singletonStrategy, - - /// Factory-based binding. - factoryStrategy, - - /// Async-based binding. - asyncStrategy, -} - -/// Enum to represent which scenario is constructed for the benchmark. -enum UniversalScenario { - /// Single registration. - register, - /// Chain of dependencies. - chain, - /// Named registrations. - named, - /// Child-scope override scenario. - override, - /// Asynchronous chain scenario. - asyncChain, -} - -/// Test module that generates a chain of service bindings for benchmarking. -/// -/// Configurable by chain count, nesting depth, binding mode, and scenario -/// to support various DI performance tests (singleton, factory, async, etc). -class UniversalChainModule extends Module { - /// Number of chains to create. - final int chainCount; - /// Depth of each chain. - final int nestingDepth; - /// How modules are registered (factory/singleton/async). - final UniversalBindingMode bindingMode; - /// Which di scenario to generate (chained, named, etc). - final UniversalScenario scenario; - - /// Constructs a configured test DI module for the benchmarks. - UniversalChainModule({ - required this.chainCount, - required this.nestingDepth, - this.bindingMode = UniversalBindingMode.singletonStrategy, - this.scenario = UniversalScenario.chain, - }); - - @override - void builder(Scope currentScope) { - if (scenario == UniversalScenario.asyncChain) { - // Generate async chain with singleton async bindings. - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - bind() - .toProvideAsync(() async { - final prev = level > 1 - ? await currentScope.resolveAsync(named: prevDepName) - : null; - return UniversalServiceImpl( - value: depName, - dependency: prev, - ); - }) - .withName(depName) - .singleton(); - } - } - return; - } - - switch (scenario) { - case UniversalScenario.register: - // Simple singleton registration. - bind() - .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) - .singleton(); - break; - case UniversalScenario.named: - // Named factory registration for two distinct objects. - bind().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); - bind().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); - break; - case UniversalScenario.chain: - // Chain of nested services, with dependency on previous level by name. - for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { - for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { - final chain = chainIndex + 1; - final level = levelIndex + 1; - final prevDepName = '${chain}_${level - 1}'; - final depName = '${chain}_$level'; - switch (bindingMode) { - case UniversalBindingMode.singletonStrategy: - bind() - .toProvide(() => UniversalServiceImpl( - value: depName, - dependency: currentScope.tryResolve(named: prevDepName), - )) - .withName(depName) - .singleton(); - break; - case UniversalBindingMode.factoryStrategy: - bind() - .toProvide(() => UniversalServiceImpl( - value: depName, - dependency: currentScope.tryResolve(named: prevDepName), - )) - .withName(depName); - break; - case UniversalBindingMode.asyncStrategy: - bind() - .toProvideAsync(() async => UniversalServiceImpl( - value: depName, - dependency: await currentScope.resolveAsync(named: prevDepName), - )) - .withName(depName) - .singleton(); - break; - } - } - } - // Регистрация алиаса без имени (на последний элемент цепочки) - final depName = '${chainCount}_$nestingDepth'; - bind() - .toProvide(() => currentScope.resolve(named: depName)) - .singleton(); - break; - case UniversalScenario.override: - // handled at benchmark level, но алиас нужен прямо в этом scope! - final depName = '${chainCount}_$nestingDepth'; - bind() - .toProvide(() => currentScope.resolve(named: depName)) - .singleton(); - break; - case UniversalScenario.asyncChain: - // already handled above - break; - } - } -} diff --git a/benchmark_di/lib/scenarios/universal_scenario.dart b/benchmark_di/lib/scenarios/universal_scenario.dart new file mode 100644 index 0000000..59857aa --- /dev/null +++ b/benchmark_di/lib/scenarios/universal_scenario.dart @@ -0,0 +1,13 @@ +/// Enum to represent which scenario is constructed for the benchmark. +enum UniversalScenario { + /// Single registration. + register, + /// Chain of dependencies. + chain, + /// Named registrations. + named, + /// Child-scope override scenario. + override, + /// Asynchronous chain scenario. + asyncChain, +} From 75db42428c9ba651e14ba46cf188780f33d8a553 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 14:23:06 +0300 Subject: [PATCH 30/32] docs: update README (en/ru) to reflect adapter-based universalRegistration pattern - Show type-safe DIAdapter-centric registration for all scenarios - Document new way to add scenarios/DI via universalRegistration - Remove legacy function-based registration/manual switching from guide - Provide examples for Dart and CLI usage with all DI adapters --- benchmark_di/README.md | 90 ++++++++++++++++++++++++++++++++++++++- benchmark_di/README.ru.md | 45 ++++++++++++++++++-- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/benchmark_di/README.md b/benchmark_di/README.md index e93506c..56a88ff 100644 --- a/benchmark_di/README.md +++ b/benchmark_di/README.md @@ -88,11 +88,97 @@ Switch DI with the CLI option: `--di` --- +## Universal DI registration: Adapter-centric approach + +Starting from vX.Y.Z, all DI registration scenarios and logic are encapsulated in the adapter itself via the `universalRegistration` method. + +### How to use (in Dart code): + +```dart +final di = CherrypickDIAdapter(); // or GetItAdapter(), RiverpodAdapter(), etc + +di.setupDependencies( + di.universalRegistration( + scenario: UniversalScenario.chain, + chainCount: 10, + nestingDepth: 5, + bindingMode: UniversalBindingMode.singletonStrategy, + ), +); +``` +- There is **no more need to use any global function or switch**: each adapter provides its own type-safe implementation. + +### How to add a new scenario or DI: +- Implement `universalRegistration(...)` in your adapter +- Use your own Enum if you want adapter-specific scenarios! +- Benchmarks and CLI become automatically extensible for custom DI and scenarios. + +### CLI usage (runs all universal scenarios for Cherrypick, GetIt, Riverpod): + +``` +dart run bin/main.dart --di=cherrypick --benchmark=all +dart run bin/main.dart --di=getit --benchmark=all +dart run bin/main.dart --di=riverpod --benchmark=all +``` + +See the `benchmark_di/lib/di_adapters/` folder for ready-to-use adapters. + +--- +## Advantages + +- **Type-safe:** Zero dynamic/object usage in DI flows. +- **Extensible:** New scenarios are just new Enum values and a method extension. +- **No global registration logic:** All DI-related logic is where it belongs: in the adapter. + +======= ## How to Add Your Own DI 1. Implement a class extending `DIAdapter` (`lib/di_adapters/your_adapter.dart`) -2. Register it in CLI (see `cli/benchmark_cli.dart`) -3. Add registration logic to `di_universal_registration.dart` to build chains for your DI +2. Implement the `universalRegistration(...)` method directly in your adapter for type-safe and scenario-specific registration +3. Register your adapter in CLI (see `cli/benchmark_cli.dart`) +4. No global function needed — all logic is within the adapter! + +--- +## Universal DI registration: Adapter-centric approach + +Starting from vX.Y.Z, all DI registration scenarios and logic are encapsulated in the adapter itself via the `universalRegistration` method. + +### How to use (in Dart code): + +```dart +final di = CherrypickDIAdapter(); // or GetItAdapter(), RiverpodAdapter(), etc + +di.setupDependencies( + di.universalRegistration( + scenario: UniversalScenario.chain, + chainCount: 10, + nestingDepth: 5, + bindingMode: UniversalBindingMode.singletonStrategy, + ), +); +``` +- There is **no more need to use any global function or switch**: each adapter provides its own type-safe implementation. + +### How to add a new scenario or DI: +- Implement `universalRegistration(...)` in your adapter +- Use your own Enum if you want adapter-specific scenarios! +- Benchmarks and CLI become automatically extensible for custom DI and scenarios. + +### CLI usage (runs all universal scenarios for Cherrypick, GetIt, Riverpod): + +``` +dart run bin/main.dart --di=cherrypick --benchmark=all +dart run bin/main.dart --di=getit --benchmark=all +dart run bin/main.dart --di=riverpod --benchmark=all +``` + +See the `benchmark_di/lib/di_adapters/` folder for ready-to-use adapters. + +## Advantages + +- **Type-safe:** Zero dynamic/object usage in DI flows. +- **Extensible:** New scenarios are just new Enum values and a method extension. +- **No global registration logic:** All DI-related logic is where it belongs: in the adapter. --- diff --git a/benchmark_di/README.ru.md b/benchmark_di/README.ru.md index 9e80424..d907963 100644 --- a/benchmark_di/README.ru.md +++ b/benchmark_di/README.ru.md @@ -88,11 +88,48 @@ benchmark_di — это современный фреймворк для изм --- -## Как добавить свой DI +## Универсальная регистрация зависимостей: теперь через adapter + +В версии X.Y.Z вся логика сценариев регистрации DI-инфраструктуры локализована в adapter через метод `universalRegistration`. + +### Использование в Dart: + +```dart +final di = CherrypickDIAdapter(); // или GetItAdapter(), RiverpodAdapter() и т.д. + +di.setupDependencies( + di.universalRegistration( + scenario: UniversalScenario.chain, + chainCount: 10, + nestingDepth: 5, + bindingMode: UniversalBindingMode.singletonStrategy, + ), +); +``` +- **Теперь нет необходимости вызывать глобальные функции или switch-case по типу DI!** Каждый adapter сам предоставляет типобезопасную функцию регистрации. + +### Как добавить новый сценарий или DI: + +- Реализуйте метод `universalRegistration(...)` в своём adapter. +- Можно использовать как UniversalScenario, так и собственные enum-сценарии! +- Бенчмарки CLI автоматически расширяются под ваш DI и ваши сценарии, если реализован метод-расширение. + +### Запуск CLI (все сценарии DI Cherrypick, GetIt, Riverpod): + +``` +dart run bin/main.dart --di=cherrypick --benchmark=all +dart run bin/main.dart --di=getit --benchmark=all +dart run bin/main.dart --di=riverpod --benchmark=all +``` + +Смотрите примеры готовых adapters в `benchmark_di/lib/di_adapters/`. + +## Преимущества + +- **Type-safe:** Исключено использование dynamic/object в стороне DI. +- **Масштабируемость:** Новый сценарий — просто enum + метод в adapter. +- **Вся логика регистрации теперь только в adapter:** Добавление или изменение не затрагивает глобальные функции. -1. Реализуйте класс-адаптер, реализующий `DIAdapter` (`lib/di_adapters/ваш_adapter.dart`) -2. Зарегистрируйте его в CLI (`cli/benchmark_cli.dart`) -3. Дополните универсальную функцию регистрации (`di_universal_registration.dart`), чтобы строить цепочки для вашего DI --- From e1371f703876f78ec568b08030b47beb94bd8427 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 14:37:34 +0300 Subject: [PATCH 31/32] docs: update architecture diagram in README to show adapter-centric universalRegistration pattern --- benchmark_di/README.md | 28 ++++++++++++++-------------- benchmark_di/README.ru.md | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/benchmark_di/README.md b/benchmark_di/README.md index 56a88ff..597c7f5 100644 --- a/benchmark_di/README.md +++ b/benchmark_di/README.md @@ -189,27 +189,29 @@ classDiagram class BenchmarkCliRunner { +run(args) } - class UniversalChainBenchmark { + class UniversalChainBenchmark~TContainer~ { +setup() +run() +teardown() } - class UniversalChainAsyncBenchmark { + class UniversalChainAsyncBenchmark~TContainer~ { +setup() +run() +teardown() } - class DIAdapter { + class DIAdapter~TContainer~ { <> +setupDependencies(cb) - +resolve(named) - +resolveAsync(named) + +resolve~T~(named) + +resolveAsync~T~(named) +teardown() +openSubScope(name) +waitForAsyncReady() + +universalRegistration(...) } class CherrypickDIAdapter class GetItAdapter + class RiverpodAdapter class UniversalChainModule { +builder(scope) +chainCount @@ -225,15 +227,12 @@ classDiagram class UniversalServiceImpl { +UniversalServiceImpl(value, dependency) } - class di_universal_registration { - +getUniversalRegistration(adapter, ...) - } class Scope class UniversalScenario class UniversalBindingMode %% Relationships - + BenchmarkCliRunner --> UniversalChainBenchmark BenchmarkCliRunner --> UniversalChainAsyncBenchmark @@ -242,9 +241,11 @@ classDiagram DIAdapter <|.. CherrypickDIAdapter DIAdapter <|.. GetItAdapter + DIAdapter <|.. RiverpodAdapter CherrypickDIAdapter ..> Scope GetItAdapter ..> GetIt: "uses GetIt" + RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry" DIAdapter o--> UniversalChainModule : setupDependencies @@ -255,11 +256,10 @@ classDiagram UniversalService <|.. UniversalServiceImpl UniversalServiceImpl --> UniversalService : dependency - BenchmarkCliRunner ..> di_universal_registration : uses - di_universal_registration ..> DIAdapter - - UniversalChainBenchmark ..> di_universal_registration : uses registrar - UniversalChainAsyncBenchmark ..> di_universal_registration : uses registrar + %% + %% Each concrete adapter implements universalRegistration + %% You can add new scenario enums for custom adapters + %% Extensibility is achieved via adapter logic, not global functions ``` --- diff --git a/benchmark_di/README.ru.md b/benchmark_di/README.ru.md index d907963..192291b 100644 --- a/benchmark_di/README.ru.md +++ b/benchmark_di/README.ru.md @@ -140,27 +140,29 @@ classDiagram class BenchmarkCliRunner { +run(args) } - class UniversalChainBenchmark { + class UniversalChainBenchmark~TContainer~ { +setup() +run() +teardown() } - class UniversalChainAsyncBenchmark { + class UniversalChainAsyncBenchmark~TContainer~ { +setup() +run() +teardown() } - class DIAdapter { + class DIAdapter~TContainer~ { <> +setupDependencies(cb) - +resolve(named) - +resolveAsync(named) + +resolve~T~(named) + +resolveAsync~T~(named) +teardown() +openSubScope(name) +waitForAsyncReady() + +universalRegistration(...) } class CherrypickDIAdapter class GetItAdapter + class RiverpodAdapter class UniversalChainModule { +builder(scope) +chainCount @@ -176,15 +178,12 @@ classDiagram class UniversalServiceImpl { +UniversalServiceImpl(value, dependency) } - class di_universal_registration { - +getUniversalRegistration(adapter, ...) - } class Scope class UniversalScenario class UniversalBindingMode %% Relationships - + BenchmarkCliRunner --> UniversalChainBenchmark BenchmarkCliRunner --> UniversalChainAsyncBenchmark @@ -193,9 +192,11 @@ classDiagram DIAdapter <|.. CherrypickDIAdapter DIAdapter <|.. GetItAdapter + DIAdapter <|.. RiverpodAdapter CherrypickDIAdapter ..> Scope GetItAdapter ..> GetIt: "uses GetIt" + RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry" DIAdapter o--> UniversalChainModule : setupDependencies @@ -206,11 +207,10 @@ classDiagram UniversalService <|.. UniversalServiceImpl UniversalServiceImpl --> UniversalService : dependency - BenchmarkCliRunner ..> di_universal_registration : uses - di_universal_registration ..> DIAdapter - - UniversalChainBenchmark ..> di_universal_registration : uses registrar - UniversalChainAsyncBenchmark ..> di_universal_registration : uses registrar + %% + %% Each concrete adapter implements universalRegistration + %% You can add new scenario enums for custom adapters + %% Extensibility is achieved via adapter logic, not global functions ``` --- From d23d06f98e5f7415a938cc42d0041e04850f96da Mon Sep 17 00:00:00 2001 From: yarashevich_kv Date: Thu, 7 Aug 2025 14:54:57 +0300 Subject: [PATCH 32/32] impr: BENCHMARK - fix for CherrypickDIAdapter. --- benchmark_di/lib/di_adapters/cherrypick_adapter.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart index 4cc4330..0bf06cd 100644 --- a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -163,11 +163,11 @@ class CherrypickDIAdapter extends DIAdapter { @override T resolve({String? named}) => - named == null ? _scope!.resolve() : _scope!.resolve(named: named); + _scope!.resolve(named: named); @override Future resolveAsync({String? named}) async => - named == null ? await _scope!.resolveAsync() : await _scope!.resolveAsync(named: named); + _scope!.resolveAsync(named: named); @override void teardown() {