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