Add English documentation comments to all benchmark_cherrypick source files (adapters, scenarios, CLI, reporters, runner)

This commit is contained in:
Sergey Penkovsky
2025-08-06 23:15:28 +03:00
parent 01d82e1cd3
commit 134fc5207a
12 changed files with 114 additions and 1 deletions

View File

@@ -12,7 +12,13 @@ import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart';
import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart';
import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart'; import 'package:benchmark_cherrypick/di_adapters/cherrypick_adapter.dart';
/// Command-line interface (CLI) runner for benchmarks.
///
/// Parses CLI arguments, orchestrates benchmarks for different
/// scenarios and configurations, collects results, and generates reports
/// in the desired output format.
class BenchmarkCliRunner { class BenchmarkCliRunner {
/// Runs benchmarks based on CLI [args], configuring different test scenarios.
Future<void> run(List<String> args) async { Future<void> run(List<String> args) async {
final config = parseBenchmarkCli(args); final config = parseBenchmarkCli(args);
final results = <Map<String, dynamic>>[]; final results = <Map<String, dynamic>>[];

View File

@@ -3,15 +3,23 @@ import 'dart:io';
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart'; import 'package:benchmark_cherrypick/scenarios/universal_chain_module.dart';
/// Enum describing all supported Universal DI benchmark types.
enum UniversalBenchmark { enum UniversalBenchmark {
/// Simple singleton registration benchmark
registerSingleton, registerSingleton,
/// Chain of singleton dependencies
chainSingleton, chainSingleton,
/// Chain using factories
chainFactory, chainFactory,
/// Async chain resolution
chainAsync, chainAsync,
/// Named registration benchmark
named, named,
/// Override/child-scope benchmark
override, override,
} }
/// Maps [UniversalBenchmark] to the scenario enum for DI chains.
UniversalScenario toScenario(UniversalBenchmark b) { UniversalScenario toScenario(UniversalBenchmark b) {
switch (b) { switch (b) {
case UniversalBenchmark.registerSingleton: case UniversalBenchmark.registerSingleton:
@@ -29,6 +37,7 @@ UniversalScenario toScenario(UniversalBenchmark b) {
} }
} }
/// Maps benchmark to registration mode (singleton/factory/async).
UniversalBindingMode toMode(UniversalBenchmark b) { UniversalBindingMode toMode(UniversalBenchmark b) {
switch (b) { switch (b) {
case UniversalBenchmark.registerSingleton: case UniversalBenchmark.registerSingleton:
@@ -46,6 +55,7 @@ UniversalBindingMode toMode(UniversalBenchmark b) {
} }
} }
/// Utility to parse a string into its corresponding enum value [T].
T parseEnum<T>(String value, List<T> values, T defaultValue) { T parseEnum<T>(String value, List<T> values, T defaultValue) {
return values.firstWhere( return values.firstWhere(
(v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(), (v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(),
@@ -53,15 +63,23 @@ T parseEnum<T>(String value, List<T> values, T defaultValue) {
); );
} }
/// Parses comma-separated integer list from [s].
List<int> parseIntList(String s) => List<int> parseIntList(String s) =>
s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList(); s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList();
/// CLI config describing what and how to benchmark.
class BenchmarkCliConfig { class BenchmarkCliConfig {
/// Benchmarks enabled to run (scenarios).
final List<UniversalBenchmark> benchesToRun; final List<UniversalBenchmark> benchesToRun;
/// List of chain counts (parallel, per test).
final List<int> chainCounts; final List<int> chainCounts;
/// List of nesting depths (max chain length, per test).
final List<int> nestDepths; final List<int> nestDepths;
/// How many times to repeat each trial.
final int repeats; final int repeats;
/// How many times to warm-up before measuring.
final int warmups; final int warmups;
/// Output report format.
final String format; final String format;
BenchmarkCliConfig({ BenchmarkCliConfig({
required this.benchesToRun, required this.benchesToRun,
@@ -73,6 +91,8 @@ class BenchmarkCliConfig {
}); });
} }
/// Parses CLI arguments [args] into a [BenchmarkCliConfig].
/// Supports --benchmark, --chainCount, --nestingDepth, etc.
BenchmarkCliConfig parseBenchmarkCli(List<String> args) { BenchmarkCliConfig parseBenchmarkCli(List<String> args) {
final parser = ArgParser() final parser = ArgParser()
..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton') ..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton')

View File

@@ -1,11 +1,14 @@
import 'report_generator.dart'; import 'report_generator.dart';
/// Generates a CSV-formatted report for benchmark results.
class CsvReport extends ReportGenerator { class CsvReport extends ReportGenerator {
/// List of all keys/columns to include in the CSV output.
@override @override
final List<String> keys = [ final List<String> keys = [
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', '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' 'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb'
]; ];
/// Renders rows as a CSV table string.
@override @override
String render(List<Map<String, dynamic>> rows) { String render(List<Map<String, dynamic>> rows) {
final header = keys.join(','); final header = keys.join(',');

View File

@@ -1,8 +1,11 @@
import 'report_generator.dart'; import 'report_generator.dart';
/// Generates a JSON-formatted report for benchmark results.
class JsonReport extends ReportGenerator { class JsonReport extends ReportGenerator {
/// No specific keys; outputs all fields in raw map.
@override @override
List<String> get keys => []; List<String> get keys => [];
/// Renders all result rows as a pretty-printed JSON array.
@override @override
String render(List<Map<String, dynamic>> rows) { String render(List<Map<String, dynamic>> rows) {
return '[\n${rows.map((r) => ' $r').join(',\n')}\n]'; return '[\n${rows.map((r) => ' $r').join(',\n')}\n]';

View File

@@ -1,11 +1,17 @@
import 'report_generator.dart'; 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 { class MarkdownReport extends ReportGenerator {
/// List of columns (keys) to show in the Markdown table.
@override @override
final List<String> keys = [ final List<String> keys = [
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' 'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb'
]; ];
/// Friendly display names for each benchmark type.
static const nameMap = { static const nameMap = {
'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton', 'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton',
'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton', 'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton',
@@ -15,6 +21,7 @@ class MarkdownReport extends ReportGenerator {
'Universal_UniversalBenchmark.override':'Override', 'Universal_UniversalBenchmark.override':'Override',
}; };
/// Renders all results as a formatted Markdown table with aligned columns and a legend.
@override @override
String render(List<Map<String, dynamic>> rows) { String render(List<Map<String, dynamic>> rows) {
final headers = [ final headers = [
@@ -38,7 +45,7 @@ class MarkdownReport extends ReportGenerator {
].map((cell) => cell.toString()).toList(); ].map((cell) => cell.toString()).toList();
}).toList(); }).toList();
// Вычислить ширину каждой колонки // Calculate column width for pretty alignment
final all = [headers] + dataRows; final all = [headers] + dataRows;
final widths = List.generate(headers.length, (i) { final widths = List.generate(headers.length, (i) {
return all.map((row) => row[i].length).reduce((a, b) => a > b ? a : b); return all.map((row) => row[i].length).reduce((a, b) => a > b ? a : b);

View File

@@ -1,12 +1,17 @@
import 'report_generator.dart'; 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 { class PrettyReport extends ReportGenerator {
/// List of columns to output in the pretty report.
@override @override
final List<String> keys = [ final List<String> keys = [
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us', 'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb' '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 = { static const nameMap = {
'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton', 'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton',
'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton', 'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton',
@@ -16,6 +21,7 @@ class PrettyReport extends ReportGenerator {
'Universal_UniversalBenchmark.override': 'Override', 'Universal_UniversalBenchmark.override': 'Override',
}; };
/// Renders the results as a header + tab-separated value table.
@override @override
String render(List<Map<String, dynamic>> rows) { String render(List<Map<String, dynamic>> rows) {
final headers = [ final headers = [

View File

@@ -1,4 +1,9 @@
/// Abstract base for generating benchmark result reports in different formats.
///
/// Subclasses implement [render] to output results, and [keys] to define columns (if any).
abstract class ReportGenerator { abstract class ReportGenerator {
/// Renders the given [results] as a formatted string (table, markdown, csv, etc).
String render(List<Map<String, dynamic>> results); String render(List<Map<String, dynamic>> results);
/// List of output columns/keys included in the export (or [] for auto/all).
List<String> get keys; List<String> get keys;
} }

View File

@@ -3,10 +3,15 @@ import 'dart:math';
import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_benchmark.dart';
import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart'; import 'package:benchmark_cherrypick/benchmarks/universal_chain_async_benchmark.dart';
/// Holds the results for a single benchmark execution.
class BenchmarkResult { class BenchmarkResult {
/// List of timings for each run (in microseconds).
final List<num> timings; final List<num> timings;
/// Difference in memory (RSS, in KB) after running.
final int memoryDiffKb; final int memoryDiffKb;
/// Difference between peak RSS and initial RSS (in KB).
final int deltaPeakKb; final int deltaPeakKb;
/// Peak RSS memory observed (in KB).
final int peakRssKb; final int peakRssKb;
BenchmarkResult({ BenchmarkResult({
required this.timings, required this.timings,
@@ -14,6 +19,7 @@ class BenchmarkResult {
required this.deltaPeakKb, required this.deltaPeakKb,
required this.peakRssKb, required this.peakRssKb,
}); });
/// Computes a BenchmarkResult instance from run timings and memory data.
factory BenchmarkResult.collect({ factory BenchmarkResult.collect({
required List<num> timings, required List<num> timings,
required List<int> rssValues, required List<int> rssValues,
@@ -32,7 +38,10 @@ class BenchmarkResult {
} }
} }
/// Static methods to execute and time benchmarks for DI containers.
class BenchmarkRunner { class BenchmarkRunner {
/// Runs a synchronous benchmark ([UniversalChainBenchmark]) for a given number of [warmups] and [repeats].
/// Collects execution time and observed memory.
static Future<BenchmarkResult> runSync({ static Future<BenchmarkResult> runSync({
required UniversalChainBenchmark benchmark, required UniversalChainBenchmark benchmark,
required int warmups, required int warmups,
@@ -58,6 +67,8 @@ class BenchmarkRunner {
return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore); 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<BenchmarkResult> runAsync({ static Future<BenchmarkResult> runAsync({
required UniversalChainAsyncBenchmark benchmark, required UniversalChainAsyncBenchmark benchmark,
required int warmups, required int warmups,

View File

@@ -1,6 +1,11 @@
import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick/cherrypick.dart';
import 'di_adapter.dart'; import 'di_adapter.dart';
/// DIAdapter implementation for the CherryPick DI library.
///
/// Wraps a CherryPick [Scope] and provides methods
/// to setup modules, resolve dependencies, teardown,
/// and open nested sub-scopes for benchmarking.
class CherrypickDIAdapter implements DIAdapter { class CherrypickDIAdapter implements DIAdapter {
Scope? _scope; Scope? _scope;
@@ -37,6 +42,8 @@ class CherrypickDIAdapter implements DIAdapter {
} }
} }
/// Internal adapter for a CherryPick sub-scope.
/// Used for simulating child/override DI scopes in benchmarks.
class _CherrypickSubScopeAdapter extends CherrypickDIAdapter { class _CherrypickSubScopeAdapter extends CherrypickDIAdapter {
final Scope _subScope; final Scope _subScope;
_CherrypickSubScopeAdapter(this._subScope); _CherrypickSubScopeAdapter(this._subScope);

View File

@@ -1,9 +1,22 @@
import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick/cherrypick.dart';
/// Abstraction for Dependency Injection (DI) Adapter.
///
/// Provides a uniform interface to setup, resolve, and teardown DI containers/modules
/// and open sub-scopes to benchmark them under different libraries.
abstract class DIAdapter { abstract class DIAdapter {
/// Installs the provided modules into the DI container.
void setupModules(List<Module> modules); void setupModules(List<Module> modules);
/// Resolves an instance of type [T] by optional [named] tag.
T resolve<T>({String? named}); T resolve<T>({String? named});
/// Asynchronously resolves an instance of type [T] by optional [named] tag.
Future<T> resolveAsync<T>({String? named}); Future<T> resolveAsync<T>({String? named});
/// Tears down or disposes of the DI container.
void teardown(); void teardown();
/// Opens a child DI sub-scope, useful for override/child-scope benchmarks.
DIAdapter openSubScope(String name); DIAdapter openSubScope(String name);
} }

View File

@@ -1,26 +1,47 @@
import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick/cherrypick.dart';
import 'universal_service.dart'; import 'universal_service.dart';
/// Enum to represent the DI registration/binding mode.
enum UniversalBindingMode { enum UniversalBindingMode {
/// Singleton/provider binding.
singletonStrategy, singletonStrategy,
/// Factory-based binding.
factoryStrategy, factoryStrategy,
/// Async-based binding.
asyncStrategy, asyncStrategy,
} }
/// Enum to represent which scenario is constructed for the benchmark.
enum UniversalScenario { enum UniversalScenario {
/// Single registration.
register, register,
/// Chain of dependencies.
chain, chain,
/// Named registrations.
named, named,
/// Child-scope override scenario.
override, override,
/// Asynchronous chain scenario.
asyncChain, asyncChain,
} }
/// Test module that generates a chain of service bindings for benchmarking.
///
/// Configurable by chain count, nesting depth, binding mode, and scenario
/// to support various DI performance tests (singleton, factory, async, etc).
class UniversalChainModule extends Module { class UniversalChainModule extends Module {
/// Number of chains to create.
final int chainCount; final int chainCount;
/// Depth of each chain.
final int nestingDepth; final int nestingDepth;
/// How modules are registered (factory/singleton/async).
final UniversalBindingMode bindingMode; final UniversalBindingMode bindingMode;
/// Which di scenario to generate (chained, named, etc).
final UniversalScenario scenario; final UniversalScenario scenario;
/// Constructs a configured test DI module for the benchmarks.
UniversalChainModule({ UniversalChainModule({
required this.chainCount, required this.chainCount,
required this.nestingDepth, required this.nestingDepth,
@@ -31,6 +52,7 @@ class UniversalChainModule extends Module {
@override @override
void builder(Scope currentScope) { void builder(Scope currentScope) {
if (scenario == UniversalScenario.asyncChain) { if (scenario == UniversalScenario.asyncChain) {
// Generate async chain with singleton async bindings.
for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) {
for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) {
final chain = chainIndex + 1; final chain = chainIndex + 1;
@@ -56,15 +78,18 @@ class UniversalChainModule extends Module {
switch (scenario) { switch (scenario) {
case UniversalScenario.register: case UniversalScenario.register:
// Simple singleton registration.
bind<UniversalService>() bind<UniversalService>()
.toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null)) .toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null))
.singleton(); .singleton();
break; break;
case UniversalScenario.named: case UniversalScenario.named:
// Named factory registration for two distinct objects.
bind<Object>().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1'); bind<Object>().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1');
bind<Object>().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2'); bind<Object>().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2');
break; break;
case UniversalScenario.chain: case UniversalScenario.chain:
// Chain of nested services, with dependency on previous level by name.
for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) { for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) {
for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) { for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) {
final chain = chainIndex + 1; final chain = chainIndex + 1;

View File

@@ -1,10 +1,17 @@
/// Base interface for any universal service in the benchmarks.
///
/// Represents an object in the dependency chain with an identifiable value
/// and (optionally) a dependency on a previous service in the chain.
abstract class UniversalService { abstract class UniversalService {
/// String ID for this service instance (e.g. chain/level info).
final String value; final String value;
/// Optional reference to dependency service in the chain.
final UniversalService? dependency; final UniversalService? dependency;
UniversalService({required this.value, this.dependency}); UniversalService({required this.value, this.dependency});
} }
/// Default implementation for [UniversalService] used in service chains.
class UniversalServiceImpl extends UniversalService { class UniversalServiceImpl extends UniversalService {
UniversalServiceImpl({required super.value, super.dependency}); UniversalServiceImpl({required super.value, super.dependency});
} }