-
Notifications
You must be signed in to change notification settings - Fork 78
[benchmark_harness]: add a bench command #2091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
33f42dd
WIP: add a bench command
kevmoo 5c3da41
more cleanup to bench CLI
kevmoo 2ff358b
more tweaks
kevmoo bb589ca
Add copyright header
kevmoo 5cc6ad0
testing
kevmoo d206517
cleanup todos
kevmoo 66a902c
fix tests
kevmoo e970341
try to get wasm test running
kevmoo 6f570b6
only test wasm on Dart >= 3.7
kevmoo 0985fed
tigther
kevmoo c70ac24
notes
kevmoo 40348e8
silly
kevmoo 4381639
silly
kevmoo 0b06cda
Better logging
kevmoo 3927940
solid cleanup
kevmoo 1419dda
better?
kevmoo da05688
Fix flavor/flavors - actually test some of the output
kevmoo da41930
some review nits
kevmoo 6a64cf4
renaming and refactoring
kevmoo 629cb20
more refactoring
kevmoo 5b3f705
handle rest args
kevmoo 210027f
help
kevmoo 66dbd11
readme
kevmoo 26f5332
Merge remote-tracking branch 'origin/HEAD' into exe
kevmoo aad5980
review nits
kevmoo 0a612fd
and this is why we have tests
kevmoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| ## 2.3.2-wip | ||
| ## 2.4.0-wip | ||
|
|
||
| - Added a `bench` command. | ||
|
|
||
| ## 2.3.1 | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'dart:io'; | ||
|
|
||
| import 'package:benchmark_harness/src/bench_command/bench_options.dart'; | ||
| import 'package:benchmark_harness/src/bench_command/compile_and_run.dart'; | ||
|
|
||
| Future<void> main(List<String> args) async { | ||
| BenchOptions? options; | ||
|
|
||
| try { | ||
| options = BenchOptions.fromArgs(args); | ||
| if (options.help) { | ||
| print(''' | ||
| \nRuns a dart script in a number of runtimes. | ||
|
|
||
| Meant to make it easy to run a benchmark executable across runtimes to validate | ||
| performance impacts. | ||
| '''); | ||
| print(BenchOptions.usage); | ||
| return; | ||
| } | ||
|
|
||
| await compileAndRun(options); | ||
| } on FormatException catch (e) { | ||
| print(e.message); | ||
| print(BenchOptions.usage); | ||
| exitCode = 64; // command line usage error | ||
| } on BenchException catch (e, stack) { | ||
| print(e.message); | ||
| if (options?.verbose ?? true) { | ||
| print(e); | ||
| print(stack); | ||
| } | ||
| exitCode = e.exitCode; | ||
| } | ||
| } |
82 changes: 82 additions & 0 deletions
82
pkgs/benchmark_harness/lib/src/bench_command/bench_options.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'package:args/args.dart'; | ||
|
|
||
| enum RuntimeFlavor { | ||
| aot(help: 'Compile and run as a native binary.'), | ||
| jit( | ||
| help: 'Run as-is without compilation, ' | ||
| 'using the just-in-time (JIT) runtime.', | ||
| ), | ||
| js(help: 'Compile to JavaScript and run on node.'), | ||
| wasm(help: 'Compile to WebAssembly and run on node.'); | ||
|
|
||
| const RuntimeFlavor({required this.help}); | ||
|
|
||
| final String help; | ||
| } | ||
|
|
||
| class BenchOptions { | ||
| BenchOptions({ | ||
| required this.flavor, | ||
| required this.target, | ||
| this.help = false, | ||
| this.verbose = false, | ||
| }) { | ||
| if (!help && flavor.isEmpty) { | ||
| // This is the wrong exception to use, except that it's caught in the | ||
| // program, so it makes implementation easy. | ||
| throw const FormatException('At least one `flavor` must be provided', 64); | ||
| } | ||
| } | ||
|
|
||
| factory BenchOptions.fromArgs(List<String> args) { | ||
| final result = _parserForBenchOptions.parse(args); | ||
|
|
||
| if (result.rest.isNotEmpty) { | ||
| throw FormatException('All arguments must be provided via `--` options. ' | ||
| 'Not sure what to do with "${result.rest.join()}".'); | ||
| } | ||
|
|
||
| return BenchOptions( | ||
| flavor: | ||
kevmoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| result.multiOption('flavor').map(RuntimeFlavor.values.byName).toSet(), | ||
| target: result.option('target')!, | ||
| help: result.flag('help'), | ||
| verbose: result.flag('verbose'), | ||
| ); | ||
| } | ||
|
|
||
| final String target; | ||
|
|
||
| final Set<RuntimeFlavor> flavor; | ||
|
|
||
| final bool help; | ||
|
|
||
| final bool verbose; | ||
|
|
||
| static String get usage => _parserForBenchOptions.usage; | ||
|
|
||
| static final _parserForBenchOptions = ArgParser() | ||
| ..addMultiOption('flavor', | ||
| abbr: 'f', | ||
| allowed: RuntimeFlavor.values.map((e) => e.name), | ||
| allowedHelp: { | ||
| for (final flavor in RuntimeFlavor.values) flavor.name: flavor.help | ||
| }) | ||
| ..addOption('target', | ||
| defaultsTo: 'benchmark/benchmark.dart', | ||
| help: 'The target script to compile and run.') | ||
| ..addFlag('help', | ||
kevmoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| defaultsTo: false, | ||
| negatable: false, | ||
| help: 'Print usage information and quit.', | ||
| abbr: 'h') | ||
| ..addFlag('verbose', | ||
| defaultsTo: false, | ||
| negatable: false, | ||
| help: 'Print the full stack trace if an exception is thrown.', | ||
| abbr: 'v'); | ||
| } | ||
186 changes: 186 additions & 0 deletions
186
pkgs/benchmark_harness/lib/src/bench_command/compile_and_run.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| import 'dart:io'; | ||
|
|
||
| import 'bench_options.dart'; | ||
|
|
||
| // TODO(kevmoo): allow the user to specify custom flags – for compile and/or run | ||
kevmoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Future<void> compileAndRun(BenchOptions options) async { | ||
| if (!FileSystemEntity.isFileSync(options.target)) { | ||
| throw BenchException( | ||
| 'The target Dart program `${options.target}` does not exist', | ||
| 2, // standard bash code for file doesn't exist | ||
| ); | ||
| } | ||
|
|
||
| for (var mode in options.flavor) { | ||
| await _Runner(flavor: mode, target: options.target).run(); | ||
| } | ||
| } | ||
|
|
||
| class BenchException implements Exception { | ||
| const BenchException(this.message, this.exitCode) : assert(exitCode > 0); | ||
| final String message; | ||
| final int exitCode; | ||
|
|
||
| @override | ||
| String toString() => 'BenchException: $message ($exitCode)'; | ||
| } | ||
|
|
||
| /// Base name for output files. | ||
| const _outputFileRoot = 'out'; | ||
|
|
||
| /// Denote the "stage" of the compile/run step for logging. | ||
| enum _Stage { compile, run } | ||
|
|
||
| /// Base class for runtime-specific runners. | ||
| abstract class _Runner { | ||
| _Runner._({required this.target, required this.flavor}) | ||
| : assert(FileSystemEntity.isFileSync(target), '$target is not a file'); | ||
|
|
||
| factory _Runner({required RuntimeFlavor flavor, required String target}) { | ||
| return (switch (flavor) { | ||
| RuntimeFlavor.jit => _JITRunner.new, | ||
| RuntimeFlavor.aot => _AOTRunner.new, | ||
| RuntimeFlavor.js => _JSRunner.new, | ||
| RuntimeFlavor.wasm => _WasmRunner.new, | ||
| })(target: target); | ||
| } | ||
|
|
||
| final String target; | ||
| final RuntimeFlavor flavor; | ||
| late Directory _tempDirectory; | ||
|
|
||
| /// Executes the compile and run cycle. | ||
| /// | ||
| /// Takes care of creating and deleting the corresponding temp directory. | ||
| Future<void> run() async { | ||
| _tempDirectory = Directory.systemTemp | ||
| .createTempSync('bench_${DateTime.now().millisecondsSinceEpoch}_'); | ||
| try { | ||
| await _runImpl(); | ||
| } finally { | ||
| _tempDirectory.deleteSync(recursive: true); | ||
| } | ||
| } | ||
|
|
||
| /// Overridden in implementations to handle the compile and run cycle. | ||
| Future<void> _runImpl(); | ||
|
|
||
| /// Executes the specific [executable] with the provided [args]. | ||
| /// | ||
| /// Also prints out a nice message before execution denoting the [flavor] and | ||
| /// the [stage]. | ||
| Future<void> _runProc( | ||
| _Stage stage, String executable, List<String> args) async { | ||
| print(''' | ||
| \n${flavor.name.toUpperCase()} - ${stage.name.toUpperCase()} | ||
| $executable ${args.join(' ')} | ||
| '''); | ||
|
|
||
| final proc = await Process.start(executable, args, | ||
| mode: ProcessStartMode.inheritStdio); | ||
|
|
||
| final exitCode = await proc.exitCode; | ||
|
|
||
| if (exitCode != 0) { | ||
| throw ProcessException(executable, args, 'Process errored', exitCode); | ||
| } | ||
| } | ||
|
|
||
| String _outputFile(String ext) => | ||
| _tempDirectory.uri.resolve('$_outputFileRoot.$ext').toFilePath(); | ||
| } | ||
|
|
||
| class _JITRunner extends _Runner { | ||
| _JITRunner({required super.target}) : super._(flavor: RuntimeFlavor.jit); | ||
|
|
||
| @override | ||
| Future<void> _runImpl() async { | ||
| await _runProc(_Stage.run, Platform.executable, [target]); | ||
| } | ||
| } | ||
|
|
||
| class _AOTRunner extends _Runner { | ||
| _AOTRunner({required super.target}) : super._(flavor: RuntimeFlavor.aot); | ||
|
|
||
| @override | ||
| Future<void> _runImpl() async { | ||
| final outFile = _outputFile('exe'); | ||
| await _runProc(_Stage.compile, Platform.executable, [ | ||
| 'compile', | ||
kevmoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 'exe', | ||
| target, | ||
| '-o', | ||
| outFile, | ||
| ]); | ||
|
|
||
| await _runProc(_Stage.run, outFile, []); | ||
| } | ||
| } | ||
|
|
||
| class _JSRunner extends _Runner { | ||
| _JSRunner({required super.target}) : super._(flavor: RuntimeFlavor.js); | ||
|
|
||
| @override | ||
| Future<void> _runImpl() async { | ||
| final outFile = _outputFile('js'); | ||
| await _runProc(_Stage.compile, Platform.executable, [ | ||
| 'compile', | ||
| 'js', | ||
| target, | ||
| '-O4', // default for Flutter | ||
| '-o', | ||
| outFile, | ||
| ]); | ||
|
|
||
| await _runProc(_Stage.run, 'node', [outFile]); | ||
| } | ||
| } | ||
|
|
||
| class _WasmRunner extends _Runner { | ||
| _WasmRunner({required super.target}) : super._(flavor: RuntimeFlavor.wasm); | ||
|
|
||
| @override | ||
| Future<void> _runImpl() async { | ||
| final outFile = _outputFile('wasm'); | ||
| await _runProc(_Stage.compile, Platform.executable, [ | ||
| 'compile', | ||
| 'wasm', | ||
| target, | ||
| '-O2', // default for Flutter | ||
kevmoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| '-o', | ||
| outFile, | ||
| ]); | ||
|
|
||
| final jsFile = | ||
| File.fromUri(_tempDirectory.uri.resolve('$_outputFileRoot.js')); | ||
| jsFile.writeAsStringSync(_wasmInvokeScript); | ||
|
|
||
| await _runProc(_Stage.run, 'node', [jsFile.path]); | ||
| } | ||
|
|
||
| static const _wasmInvokeScript = ''' | ||
| import { readFile } from 'node:fs/promises'; // For async file reading | ||
| import { fileURLToPath } from 'url'; | ||
| import { dirname, join } from 'path'; | ||
| // Get the current directory name | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = dirname(__filename); | ||
| const wasmFilePath = join(__dirname, '$_outputFileRoot.wasm'); | ||
| const wasmBytes = await readFile(wasmFilePath); | ||
| const mjsFilePath = join(__dirname, '$_outputFileRoot.mjs'); | ||
| const dartModule = await import(mjsFilePath); | ||
| const {compile} = dartModule; | ||
| const compiledApp = await compile(wasmBytes); | ||
| const instantiatedApp = await compiledApp.instantiate({}); | ||
| await instantiatedApp.invokeMain(); | ||
| '''; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.