From 23683119c211ad1c090735490429ba46c5d64f50 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Fri, 1 Aug 2025 08:26:33 +0300 Subject: [PATCH] Add complex DI benchmarks, main runner, and English README with summarized results for cherrypick core --- benchmark_cherrypick/README.md | 42 +++++++++ benchmark_cherrypick/README.ru.md | 42 +++++++++ benchmark_cherrypick/bin/main.dart | 18 ++++ .../lib/async_chain_benchmark.dart | 40 ++++++++ .../lib/cherrypick_benchmark.dart | 37 ++++++++ .../lib/complex_bindings_benchmark.dart | 92 +++++++++++++++++++ .../lib/scope_override_benchmark.dart | 43 +++++++++ benchmark_cherrypick/pubspec.lock | 60 ++++++++++++ benchmark_cherrypick/pubspec.yaml | 15 +++ 9 files changed, 389 insertions(+) create mode 100644 benchmark_cherrypick/README.md create mode 100644 benchmark_cherrypick/README.ru.md create mode 100644 benchmark_cherrypick/bin/main.dart create mode 100644 benchmark_cherrypick/lib/async_chain_benchmark.dart create mode 100644 benchmark_cherrypick/lib/cherrypick_benchmark.dart create mode 100644 benchmark_cherrypick/lib/complex_bindings_benchmark.dart create mode 100644 benchmark_cherrypick/lib/scope_override_benchmark.dart create mode 100644 benchmark_cherrypick/pubspec.lock create mode 100644 benchmark_cherrypick/pubspec.yaml diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md new file mode 100644 index 0000000..1b1e720 --- /dev/null +++ b/benchmark_cherrypick/README.md @@ -0,0 +1,42 @@ +# benchmark_cherrypick + +Benchmarks for 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). + +## 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. + +## Benchmark results + +| Scenario | RunTime (μs) | +|----------------------------------------------------|---------------| +| RegisterAndResolve | 0.1976 | +| ChainSingleton (A->B->C, singleton) | 0.2721 | +| ChainFactory (A->B->C, factory) | 0.6690 | +| NamedResolve (by name) | 0.2018 | +| AsyncChain (A->B->C, async) | 1.2732 | +| ScopeOverride (child overrides parent) | 0.1962 | + +## How to run + +1. Get dependencies: + ```shell + dart pub get + ``` +2. Run the benchmarks: + ```shell + dart run bin/main.dart + ``` + +A text report with all metrics will be displayed in the console. + +--- + +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. diff --git a/benchmark_cherrypick/README.ru.md b/benchmark_cherrypick/README.ru.md new file mode 100644 index 0000000..9f05492 --- /dev/null +++ b/benchmark_cherrypick/README.ru.md @@ -0,0 +1,42 @@ +# benchmark_cherrypick + +Бенчмарки производительности и функциональности DI-контейнера cherrypick (core). + +Все сценарии используют реальные возможности public API cherrypick (scope, module, binding, scoping и асинхронность). + +## Сценарии + +- **RegisterAndResolve**: базовая операция регистрации и разрешения зависимости. +- **ChainSingleton (A->B->C, singleton)**: цепочка зависимостей, все singletons. +- **ChainFactory (A->B->C, factory)**: цепочка зависимостей с factory биндингами, новые объекты на каждый запрос. +- **NamedResolve (by name)**: разрешение именованной зависимости среди нескольких реализаций. +- **AsyncChain (A->B->C, async)**: асинхронная цепочка зависимостей. +- **ScopeOverride (child overrides parent)**: переопределение зависимости в дочернем scope над родительским. + +## Результаты исследования + +| Сценарий | RunTime (мкс) | +|----------------------------------------------------|--------------| +| RegisterAndResolve | 0.1976 | +| ChainSingleton (A->B->C, singleton) | 0.2721 | +| ChainFactory (A->B->C, factory) | 0.6690 | +| NamedResolve (by name) | 0.2018 | +| AsyncChain (A->B->C, async) | 1.2732 | +| ScopeOverride (child overrides parent) | 0.1962 | + +## Как запускать + +1. Получить зависимости: + ```shell + dart pub get + ``` +2. Запустить бенчмарк: + ```shell + dart run bin/main.dart + ``` + +Будет показан текстовый отчёт по всем метрикам. + +--- + +Если хотите добавить свой сценарий — создайте отдельный Dart-файл и объявите новый BenchmarkBase/AsyncBenchmarkBase, не забудьте вставить его вызов в main. diff --git a/benchmark_cherrypick/bin/main.dart b/benchmark_cherrypick/bin/main.dart new file mode 100644 index 0000000..941a0c6 --- /dev/null +++ b/benchmark_cherrypick/bin/main.dart @@ -0,0 +1,18 @@ +import 'package:benchmark_runner/benchmark_runner.dart'; +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'; + +void main(List args) async { + // Синхронные бенчмарки + RegisterAndResolveBenchmark().report(); + ChainSingletonBenchmark().report(); + ChainFactoryBenchmark().report(); + NamedResolveBenchmark().report(); + + // Асинхронный бенчмарк + await AsyncChainBenchmark().report(); + + ScopeOverrideBenchmark().report(); +} diff --git a/benchmark_cherrypick/lib/async_chain_benchmark.dart b/benchmark_cherrypick/lib/async_chain_benchmark.dart new file mode 100644 index 0000000..a63e790 --- /dev/null +++ b/benchmark_cherrypick/lib/async_chain_benchmark.dart @@ -0,0 +1,40 @@ +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 { + AsyncChainBenchmark() : super('AsyncChain (A->B->C, async)'); + late Scope scope; + + @override + Future setup() async { + scope = CherryPick.openRootScope(); + scope.installModules([AsyncChainModule()]); + } + @override + Future teardown() async { + CherryPick.closeRootScope(); + } + @override + Future run() async { + await scope.resolveAsync(); + } +} diff --git a/benchmark_cherrypick/lib/cherrypick_benchmark.dart b/benchmark_cherrypick/lib/cherrypick_benchmark.dart new file mode 100644 index 0000000..c6e8a93 --- /dev/null +++ b/benchmark_cherrypick/lib/cherrypick_benchmark.dart @@ -0,0 +1,37 @@ +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 {} + +class RegisterAndResolveBenchmark extends BenchmarkBase { + RegisterAndResolveBenchmark() : super('RegisterAndResolve'); + late final Scope scope; + + @override + void setup() { + scope = CherryPick.openRootScope(); + scope.installModules([AppModule()]); + + } + + @override + void run() { + scope.resolve(); + } + + @override + void teardown() => CherryPick.closeRootScope(); +} + +void main() { + RegisterAndResolveBenchmark().report(); +} diff --git a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart new file mode 100644 index 0000000..5a021ca --- /dev/null +++ b/benchmark_cherrypick/lib/complex_bindings_benchmark.dart @@ -0,0 +1,92 @@ +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); +} +class ServiceC { + final ServiceB b; + ServiceC(this.b); +} + +class ChainSingletonModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => ServiceA()).singleton(); + bind().toProvide((() => ServiceB(currentScope.resolve()))).singleton(); + bind().toProvide((() => ServiceC(currentScope.resolve()))).singleton(); + } +} + +class ChainSingletonBenchmark extends BenchmarkBase { + ChainSingletonBenchmark() : super('ChainSingleton (A->B->C, singleton)'); + late Scope scope; + @override + void setup() { + scope = CherryPick.openRootScope(); + scope.installModules([ChainSingletonModule()]); + } + @override + void teardown() => CherryPick.closeRootScope(); + @override + void run() { + scope.resolve(); + } +} + +// === DI graph: A -> B -> C (factory/no singleton) === +class ChainFactoryModule extends Module { + @override + void builder(Scope currentScope) { + bind().toProvide(() => ServiceA()); + bind().toProvide((() => ServiceB(currentScope.resolve()))); + bind().toProvide((() => ServiceC(currentScope.resolve()))); + } +} + +class ChainFactoryBenchmark extends BenchmarkBase { + ChainFactoryBenchmark() : super('ChainFactory (A->B->C, factory)'); + late Scope scope; + @override + void setup() { + scope = CherryPick.openRootScope(); + scope.installModules([ChainFactoryModule()]); + } + @override + void teardown() => CherryPick.closeRootScope(); + @override + void run() { + scope.resolve(); + } +} + +// === 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 { + 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'); + } +} diff --git a/benchmark_cherrypick/lib/scope_override_benchmark.dart b/benchmark_cherrypick/lib/scope_override_benchmark.dart new file mode 100644 index 0000000..7647504 --- /dev/null +++ b/benchmark_cherrypick/lib/scope_override_benchmark.dart @@ -0,0 +1,43 @@ +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 { + ScopeOverrideBenchmark() : super('ScopeOverride (child overrides parent)'); + late Scope parent; + late Scope child; + @override + void setup() { + parent = CherryPick.openRootScope(); + parent.installModules([ParentModule()]); + child = parent.openSubScope('child'); + child.installModules([ChildOverrideModule()]); + } + @override + void teardown() { + CherryPick.closeRootScope(); + } + @override + void run() { + // Должен возвращать ChildImpl, а не ParentImpl + final resolved = child.resolve(); + assert(resolved is ChildImpl); + } +} diff --git a/benchmark_cherrypick/pubspec.lock b/benchmark_cherrypick/pubspec.lock new file mode 100644 index 0000000..c67642e --- /dev/null +++ b/benchmark_cherrypick/pubspec.lock @@ -0,0 +1,60 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + ansi_modifier: + dependency: transitive + description: + name: ansi_modifier + sha256: "4b97c241f345e49c929bd56d0198b567b7dfcca7ec8d4f798745c9ced998684c" + url: "https://pub.dev" + source: hosted + version: "0.1.4" + benchmark_harness: + dependency: "direct dev" + description: + name: benchmark_harness + sha256: "83f65107165883ba8623eb822daacb23dcf9f795c66841de758c9dd7c5a0cf28" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + benchmark_runner: + dependency: "direct dev" + description: + name: benchmark_runner + sha256: "7de181228eb74cb34507ded2260fe88b3b71e0aacfe0dfa794df49edaf041ca3" + url: "https://pub.dev" + source: hosted + version: "0.0.4" + cherrypick: + dependency: "direct main" + description: + path: "../cherrypick" + relative: true + source: path + version: "2.2.0" + exception_templates: + dependency: transitive + description: + name: exception_templates + sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + lazy_memo: + dependency: transitive + description: + name: lazy_memo + sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d + url: "https://pub.dev" + source: hosted + version: "0.2.3" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" +sdks: + dart: ">=3.5.2 <4.0.0" diff --git a/benchmark_cherrypick/pubspec.yaml b/benchmark_cherrypick/pubspec.yaml new file mode 100644 index 0000000..37fde2b --- /dev/null +++ b/benchmark_cherrypick/pubspec.yaml @@ -0,0 +1,15 @@ +name: benchmark_cherrypick +version: 0.1.0 +publish_to: none +description: Benchmark for cherrypick core DI library + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + cherrypick: + path: ../cherrypick + +dev_dependencies: + benchmark_harness: ^2.2.2 + benchmark_runner: ^0.0.2