diff --git a/benchmark_cherrypick/README.md b/benchmark_cherrypick/README.md deleted file mode 100644 index 81fe8a2..0000000 --- a/benchmark_cherrypick/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# 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.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 | - -## 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 deleted file mode 100644 index 86a9539..0000000 --- a/benchmark_cherrypick/README.ru.md +++ /dev/null @@ -1,42 +0,0 @@ -# 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.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 | - -## Как запускать - -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 deleted file mode 100644 index 7614e7c..0000000 --- a/benchmark_cherrypick/bin/main.dart +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 59f74a6..0000000 --- a/benchmark_cherrypick/lib/async_chain_benchmark.dart +++ /dev/null @@ -1,43 +0,0 @@ -// 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 { - AsyncChainBenchmark() : super('AsyncChain (A->B->C, async)'); - late Scope scope; - - @override - Future setup() async { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - 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 deleted file mode 100644 index a6355bf..0000000 --- a/benchmark_cherrypick/lib/cherrypick_benchmark.dart +++ /dev/null @@ -1,40 +0,0 @@ -// 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 {} - -class RegisterAndResolveBenchmark extends BenchmarkBase { - RegisterAndResolveBenchmark() : super('RegisterAndResolve'); - late final Scope scope; - - @override - void setup() { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - 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 deleted file mode 100644 index 47b352c..0000000 --- a/benchmark_cherrypick/lib/complex_bindings_benchmark.dart +++ /dev/null @@ -1,95 +0,0 @@ -// ignore: depend_on_referenced_packages -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() { - CherryPick.disableGlobalCycleDetection(); - CherryPick.disableGlobalCrossScopeCycleDetection(); - 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 deleted file mode 100644 index b3231dd..0000000 --- a/benchmark_cherrypick/lib/scope_override_benchmark.dart +++ /dev/null @@ -1,46 +0,0 @@ -// 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 { - 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'); - 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_di/README.md b/benchmark_di/README.md new file mode 100644 index 0000000..597c7f5 --- /dev/null +++ b/benchmark_di/README.md @@ -0,0 +1,275 @@ +# benchmark_di + +_Benchmark suite for cherrypick DI container, get_it, and other DI solutions._ + +## Overview + +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) +- 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**: 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. **Install dependencies:** + ```shell + dart pub get + ``` + +2. **Run all benchmarks (default: all scenarios, 2 warmup, 2 repeats):** + ```shell + dart run bin/main.dart --benchmark=all --format=markdown + ``` + +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 + +- `--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 + +### Run Examples + +- **All benchmarks for cherrypick:** + ```shell + dart run bin/main.dart --di=cherrypick --benchmark=all --format=markdown + ``` + +- **For get_it (all scenarios):** + ```shell + dart run bin/main.dart --di=getit --benchmark=all --format=markdown + ``` + +- **Specify chains/depth matrix:** + ```shell + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv + ``` + +--- + +## 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. 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. + +--- + +## Architecture + +```mermaid +classDiagram + class BenchmarkCliRunner { + +run(args) + } + class UniversalChainBenchmark~TContainer~ { + +setup() + +run() + +teardown() + } + class UniversalChainAsyncBenchmark~TContainer~ { + +setup() + +run() + +teardown() + } + class DIAdapter~TContainer~ { + <> + +setupDependencies(cb) + +resolve~T~(named) + +resolveAsync~T~(named) + +teardown() + +openSubScope(name) + +waitForAsyncReady() + +universalRegistration(...) + } + class CherrypickDIAdapter + class GetItAdapter + class RiverpodAdapter + class UniversalChainModule { + +builder(scope) + +chainCount + +nestingDepth + +bindingMode + +scenario + } + class UniversalService { + <> + +value + +dependency + } + class UniversalServiceImpl { + +UniversalServiceImpl(value, dependency) + } + class Scope + class UniversalScenario + class UniversalBindingMode + + %% Relationships + + BenchmarkCliRunner --> UniversalChainBenchmark + BenchmarkCliRunner --> UniversalChainAsyncBenchmark + + UniversalChainBenchmark *-- DIAdapter + UniversalChainAsyncBenchmark *-- DIAdapter + + DIAdapter <|.. CherrypickDIAdapter + DIAdapter <|.. GetItAdapter + DIAdapter <|.. RiverpodAdapter + + CherrypickDIAdapter ..> Scope + GetItAdapter ..> GetIt: "uses GetIt" + RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry" + + DIAdapter o--> UniversalChainModule : setupDependencies + + UniversalChainModule ..> UniversalScenario + UniversalChainModule ..> UniversalBindingMode + + UniversalChainModule o-- UniversalServiceImpl : creates + UniversalService <|.. UniversalServiceImpl + UniversalServiceImpl --> UniversalService : dependency + + %% + %% Each concrete adapter implements universalRegistration + %% You can add new scenario enums for custom adapters + %% Extensibility is achieved via adapter logic, not global functions +``` + +--- + +## Metrics + +Always collected: +- **Timings** (microseconds): mean, median, stddev, min, max +- **Memory**: RSS difference, peak RSS + +## License + +MIT diff --git a/benchmark_di/README.ru.md b/benchmark_di/README.ru.md new file mode 100644 index 0000000..192291b --- /dev/null +++ b/benchmark_di/README.ru.md @@ -0,0 +1,226 @@ +# benchmark_di + +_Бенчмаркинговый набор для cherrypick, get_it и других DI-контейнеров._ + +## Общее описание + +benchmark_di — это современный фреймворк для измерения производительности DI-контейнеров (как cherrypick, так и get_it) на синтетических, сложных и реальных сценариях: цепочки зависимостей, factory, async, именованные биндинги, override и пр. + +**Возможности:** +- Универсальный слой регистрации сценариев (работает с любым 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**: Асинхронная цепочка (async factory/provider) +- **named**: Разрешение по имени (например, из нескольких реализаций) +- **override**: Переопределение зависимостей в subScope + +--- + +## Поддерживаемые DI-контейнеры + +- **cherrypick** (по умолчанию) +- **get_it** +- Легко добавить свой DI через DIAdapter + +Меняется одной CLI-опцией: `--di` + +--- + +## Как запустить + +1. **Установить зависимости:** + ```shell + dart pub get + ``` + +2. **Запустить все бенчмарки (по умолчанию: все сценарии, 2 прогрева, 2 замера):** + ```shell + dart run bin/main.dart --benchmark=all --format=markdown + ``` + +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 + +- `--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 --di=cherrypick --benchmark=all --format=markdown + ``` + +- **Для get_it (все сценарии):** + ```shell + dart run bin/main.dart --di=getit --benchmark=all --format=markdown + ``` + +- **Запуск по матрице параметров:** + ```shell + dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv + ``` + +--- + +## Универсальная регистрация зависимостей: теперь через 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:** Добавление или изменение не затрагивает глобальные функции. + + +--- + +## Архитектура + +```mermaid +classDiagram + class BenchmarkCliRunner { + +run(args) + } + class UniversalChainBenchmark~TContainer~ { + +setup() + +run() + +teardown() + } + class UniversalChainAsyncBenchmark~TContainer~ { + +setup() + +run() + +teardown() + } + class DIAdapter~TContainer~ { + <> + +setupDependencies(cb) + +resolve~T~(named) + +resolveAsync~T~(named) + +teardown() + +openSubScope(name) + +waitForAsyncReady() + +universalRegistration(...) + } + class CherrypickDIAdapter + class GetItAdapter + class RiverpodAdapter + class UniversalChainModule { + +builder(scope) + +chainCount + +nestingDepth + +bindingMode + +scenario + } + class UniversalService { + <> + +value + +dependency + } + class UniversalServiceImpl { + +UniversalServiceImpl(value, dependency) + } + class Scope + class UniversalScenario + class UniversalBindingMode + + %% Relationships + + BenchmarkCliRunner --> UniversalChainBenchmark + BenchmarkCliRunner --> UniversalChainAsyncBenchmark + + UniversalChainBenchmark *-- DIAdapter + UniversalChainAsyncBenchmark *-- DIAdapter + + DIAdapter <|.. CherrypickDIAdapter + DIAdapter <|.. GetItAdapter + DIAdapter <|.. RiverpodAdapter + + CherrypickDIAdapter ..> Scope + GetItAdapter ..> GetIt: "uses GetIt" + RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry" + + DIAdapter o--> UniversalChainModule : setupDependencies + + UniversalChainModule ..> UniversalScenario + UniversalChainModule ..> UniversalBindingMode + + UniversalChainModule o-- UniversalServiceImpl : creates + UniversalService <|.. UniversalServiceImpl + UniversalServiceImpl --> UniversalService : dependency + + %% + %% Each concrete adapter implements universalRegistration + %% You can add new scenario enums for custom adapters + %% Extensibility is achieved via adapter logic, not global functions +``` + +--- + +## Метрики + +Всегда собираются: +- **Время** (мкс): среднее, медиана, stddev, min, max +- **Память**: прирост RSS, пиковое значение RSS + +## Лицензия + +MIT 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_cherrypick/analysis_options.yaml b/benchmark_di/analysis_options.yaml similarity index 96% rename from benchmark_cherrypick/analysis_options.yaml rename to benchmark_di/analysis_options.yaml index 95b8ade..6bd1e89 100644 --- a/benchmark_cherrypick/analysis_options.yaml +++ b/benchmark_di/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_di/bin/main.dart b/benchmark_di/bin/main.dart new file mode 100644 index 0000000..985adbd --- /dev/null +++ b/benchmark_di/bin/main.dart @@ -0,0 +1,5 @@ +import 'package:benchmark_di/cli/benchmark_cli.dart'; + +Future main(List args) async { + await BenchmarkCliRunner().run(args); +} \ No newline at end of file diff --git a/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart new file mode 100644 index 0000000..eea5849 --- /dev/null +++ b/benchmark_di/lib/benchmarks/universal_chain_async_benchmark.dart @@ -0,0 +1,41 @@ +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_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.setupDependencies(di.universalRegistration( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: UniversalScenario.asyncChain, + )); + await di.waitForAsyncReady(); + } + + @override + Future teardown() async { + di.teardown(); + } + + @override + Future run() async { + final serviceName = '${chainCount}_$nestingDepth'; + await di.resolveAsync(named: serviceName); + } +} diff --git a/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart new file mode 100644 index 0000000..b7eb19f --- /dev/null +++ b/benchmark_di/lib/benchmarks/universal_chain_benchmark.dart @@ -0,0 +1,79 @@ +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_service.dart'; + +class UniversalChainBenchmark extends BenchmarkBase { + 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, + this.scenario = UniversalScenario.chain, + }) : super('Universal: $scenario/$mode CD=$chainCount/$nestingDepth'); + + @override + void setup() { + switch (scenario) { + case UniversalScenario.override: + _di.setupDependencies(_di.universalRegistration( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.chain, + )); + _childDi = _di.openSubScope('child'); + _childDi!.setupDependencies(_childDi!.universalRegistration( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: UniversalBindingMode.singletonStrategy, + scenario: UniversalScenario.chain, + )); + break; + default: + _di.setupDependencies(_di.universalRegistration( + chainCount: chainCount, + nestingDepth: nestingDepth, + bindingMode: mode, + scenario: scenario, + )); + break; + } + } + + @override + void teardown() => _di.teardown(); + + @override + void run() { + switch (scenario) { + case UniversalScenario.register: + _di.resolve(); + break; + case UniversalScenario.named: + if (_di.runtimeType.toString().contains('GetItAdapter')) { + _di.resolve(named: 'impl2'); + } else { + _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_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart new file mode 100644 index 0000000..1680f9d --- /dev/null +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -0,0 +1,133 @@ +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 'report/pretty_report.dart'; +import 'report/csv_report.dart'; +import 'report/json_report.dart'; +import 'parser.dart'; +import 'runner.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'; +import 'package:benchmark_di/di_adapters/riverpod_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 = >[]; + for (final bench in config.benchesToRun) { + final scenario = toScenario(bench); + final mode = toMode(bench); + for (final c in config.chainCounts) { + for (final d in config.nestDepths) { + BenchmarkResult benchResult; + if (config.di == 'getit') { + final di = GetItAdapter(); + if (scenario == UniversalScenario.asyncChain) { + final benchAsync = UniversalChainAsyncBenchmark(di, + chainCount: c, nestingDepth: d, mode: mode, + ); + benchResult = await BenchmarkRunner.runAsync( + benchmark: benchAsync, + warmups: config.warmups, + repeats: config.repeats, + ); + } 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 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(); + var mean = timings.reduce((a, b) => a + b) / timings.length; + var median = timings[timings.length ~/ 2]; + var minVal = timings.first; + var maxVal = timings.last; + var stddev = timings.isEmpty ? 0 : sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length); + results.add({ + 'benchmark': 'Universal_$bench', + 'chainCount': c, + 'nestingDepth': d, + 'mean_us': mean.toStringAsFixed(2), + 'median_us': median.toStringAsFixed(2), + 'stddev_us': stddev.toStringAsFixed(2), + 'min_us': minVal.toStringAsFixed(2), + 'max_us': maxVal.toStringAsFixed(2), + 'trials': timings.length, + 'timings_us': timings.map((t) => t.toStringAsFixed(2)).toList(), + 'memory_diff_kb': benchResult.memoryDiffKb, + 'delta_peak_kb': benchResult.deltaPeakKb, + 'peak_rss_kb': benchResult.peakRssKb, + }); + } + } + } + final reportGenerators = { + 'pretty': PrettyReport(), + 'csv': CsvReport(), + 'json': JsonReport(), + 'markdown': MarkdownReport(), + }; + print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results)); + } +} \ No newline at end of file diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart new file mode 100644 index 0000000..3c93ede --- /dev/null +++ b/benchmark_di/lib/cli/parser.dart @@ -0,0 +1,130 @@ +import 'dart:io'; + +import 'package:args/args.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 { + /// 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: + 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; + } +} + +/// Maps benchmark to registration mode (singleton/factory/async). +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; + } +} + +/// 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(), + orElse: () => 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; + /// Name of DI implementation ("cherrypick" or "getit") + final String di; + BenchmarkCliConfig({ + required this.benchesToRun, + required this.chainCounts, + required this.nestDepths, + required this.repeats, + required this.warmups, + required this.format, + required this.di, + }); +} + +/// 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') + ..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') + ..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) { + 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, + di: result['di'] as String? ?? 'cherrypick', + ); +} \ No newline at end of file diff --git a/benchmark_di/lib/cli/report/csv_report.dart b/benchmark_di/lib/cli/report/csv_report.dart new file mode 100644 index 0000000..6379889 --- /dev/null +++ b/benchmark_di/lib/cli/report/csv_report.dart @@ -0,0 +1,24 @@ +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(','); + 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_di/lib/cli/report/json_report.dart b/benchmark_di/lib/cli/report/json_report.dart new file mode 100644 index 0000000..fb75d67 --- /dev/null +++ b/benchmark_di/lib/cli/report/json_report.dart @@ -0,0 +1,13 @@ +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]'; + } +} \ No newline at end of file diff --git a/benchmark_di/lib/cli/report/markdown_report.dart b/benchmark_di/lib/cli/report/markdown_report.dart new file mode 100644 index 0000000..cf97ecc --- /dev/null +++ b/benchmark_di/lib/cli/report/markdown_report.dart @@ -0,0 +1,78 @@ +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', + 'Universal_UniversalBenchmark.chainFactory':'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync':'AsyncChain', + 'Universal_UniversalBenchmark.named':'Named', + '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 = [ + 'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)' + ]; + 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()).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); + }); + + 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(); + + 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 diff --git a/benchmark_di/lib/cli/report/pretty_report.dart b/benchmark_di/lib/cli/report/pretty_report.dart new file mode 100644 index 0000000..36688ef --- /dev/null +++ b/benchmark_di/lib/cli/report/pretty_report.dart @@ -0,0 +1,50 @@ +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', + 'Universal_UniversalBenchmark.chainFactory': 'ChainFactory', + 'Universal_UniversalBenchmark.chainAsync': 'AsyncChain', + 'Universal_UniversalBenchmark.named': 'Named', + 'Universal_UniversalBenchmark.override': 'Override', + }; + + /// Renders the results as a header + tab-separated value table. + @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('\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'); + } +} diff --git a/benchmark_di/lib/cli/report/report_generator.dart b/benchmark_di/lib/cli/report/report_generator.dart new file mode 100644 index 0000000..59a1e98 --- /dev/null +++ b/benchmark_di/lib/cli/report/report_generator.dart @@ -0,0 +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_di/lib/cli/runner.dart b/benchmark_di/lib/cli/runner.dart new file mode 100644 index 0000000..ae6835d --- /dev/null +++ b/benchmark_di/lib/cli/runner.dart @@ -0,0 +1,96 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart'; +import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart'; + +/// 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, + required this.memoryDiffKb, + 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, + 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(), + ); + } +} + +/// 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, + 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); + } + + /// 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, + 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 diff --git a/benchmark_di/lib/di_adapters/cherrypick_adapter.dart b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart new file mode 100644 index 0000000..0bf06cd --- /dev/null +++ b/benchmark_di/lib/di_adapters/cherrypick_adapter.dart @@ -0,0 +1,188 @@ +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'; + + +/// 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; + final bool _isSubScope; + + CherrypickDIAdapter([Scope? scope, this._isSubScope = false]) { + _scope = scope; + } + + @override + void setupDependencies(void Function(Scope container) registration) { + _scope ??= CherryPick.openRootScope(); + 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}) => + _scope!.resolve(named: named); + + @override + Future resolveAsync({String? named}) async => + _scope!.resolveAsync(named: named); + + @override + void teardown() { + if (!_isSubScope) { + CherryPick.closeRootScope(); + _scope = null; + } + // SubScope teardown не требуется + } + + @override + CherrypickDIAdapter openSubScope(String name) { + return CherrypickDIAdapter(_scope!.openSubScope(name), true); + } + + @override + Future waitForAsyncReady() async {} +} diff --git a/benchmark_di/lib/di_adapters/di_adapter.dart b/benchmark_di/lib/di_adapters/di_adapter.dart new file mode 100644 index 0000000..938d8f9 --- /dev/null +++ b/benchmark_di/lib/di_adapters/di_adapter.dart @@ -0,0 +1,32 @@ +import 'package:benchmark_di/scenarios/universal_binding_mode.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}); + + /// Асинхронно резолвит экземпляр типа [T] (если нужно). + Future resolveAsync({String? named}); + + /// Уничтожает/отчищает DI-контейнер. + void teardown(); + + /// Открывает дочерний scope и возвращает новый адаптер (если поддерживается). + DIAdapter openSubScope(String name); + + /// Ожидание готовности 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 new file mode 100644 index 0000000..0396161 --- /dev/null +++ b/benchmark_di/lib/di_adapters/get_it_adapter.dart @@ -0,0 +1,156 @@ +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'; + +/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией. +class GetItAdapter extends DIAdapter { + late GetIt _getIt; + final String? _scopeName; + final bool _isSubScope; + bool _scopePushed = false; + + /// Основной (root) и subScope-конструкторы. + GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false}) + : _scopeName = scopeName, + _isSubScope = isSubScope { + if (instance != null) { + _getIt = instance; + } + } + + @override + 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(); + } + + @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 new file mode 100644 index 0000000..c16452e --- /dev/null +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -0,0 +1,139 @@ +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'; + +/// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией. +class RiverpodAdapter extends DIAdapter>> { + rp.ProviderContainer? _container; + final Map> _namedProviders; + final rp.ProviderContainer? _parent; + final bool _isSubScope; + + RiverpodAdapter({ + rp.ProviderContainer? container, + Map>? providers, + rp.ProviderContainer? parent, + bool isSubScope = false, + }) : _container = container, + _namedProviders = providers ?? >{}, + _parent = parent, + _isSubScope = isSubScope; + + @override + void setupDependencies(void Function(Map> container) registration) { + _container ??= _parent == null + ? rp.ProviderContainer() + : rp.ProviderContainer(parent: _parent); + registration(_namedProviders); + } + + @override + T resolve({String? named}) { + final key = named ?? T.toString(); + final provider = _namedProviders[key]; + if (provider == null) { + throw Exception('Provider not found for $key'); + } + return _container!.read(provider) as T; + } + + @override + Future resolveAsync({String? named}) async { + final key = named ?? T.toString(); + final provider = _namedProviders[key]; + if (provider == null) { + throw Exception('Provider not found for $key'); + } + // Если это FutureProvider — используем .future + if (provider.runtimeType.toString().contains('FutureProvider')) { + return await _container!.read((provider as dynamic).future) as T; + } + return resolve(named: named); + } + + @override + void teardown() { + _container?.dispose(); + _container = null; + _namedProviders.clear(); + } + + @override + RiverpodAdapter openSubScope(String name) { + final newContainer = rp.ProviderContainer(parent: _container); + return RiverpodAdapter( + container: newContainer, + providers: Map.of(_namedProviders), + parent: _container, + isSubScope: true, + ); + } + + @override + Future waitForAsyncReady() async { + // 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/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_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, +} diff --git a/benchmark_di/lib/scenarios/universal_service.dart b/benchmark_di/lib/scenarios/universal_service.dart new file mode 100644 index 0000000..910201f --- /dev/null +++ b/benchmark_di/lib/scenarios/universal_service.dart @@ -0,0 +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 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 50% rename from benchmark_cherrypick/pubspec.lock rename to benchmark_di/pubspec.lock index 31ef9ca..11216c2 100644 --- a/benchmark_cherrypick/pubspec.lock +++ b/benchmark_di/pubspec.lock @@ -9,6 +9,22 @@ 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" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" benchmark_harness: dependency: "direct dev" description: @@ -31,7 +47,15 @@ packages: path: "../cherrypick" relative: true source: path - version: "3.0.0-dev.1" + 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: @@ -40,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: @@ -64,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_cherrypick/pubspec.yaml b/benchmark_di/pubspec.yaml similarity index 57% rename from benchmark_cherrypick/pubspec.yaml rename to benchmark_di/pubspec.yaml index d885d4d..87d2865 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' @@ -9,6 +9,9 @@ environment: dependencies: cherrypick: path: ../cherrypick + args: ^2.7.0 + get_it: ^8.2.0 + riverpod: ^2.6.1 dev_dependencies: lints: ^5.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 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: