mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:17:55 +00:00
1ab011a2a7
Closes https://github.com/dart-lang/sdk/pull/50879 TEST=only changes to spelling to comments and internal string messages GitOrigin-RevId: 3fa4104fa17054ea2247118a1b6213c62f2749e0 Change-Id: I776120bb2492c662dd32612dd2f6a670b5891e42 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278060 Reviewed-by: Michael Thomsen <mit@google.com> Commit-Queue: Sigmund Cherem <sigmund@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
652 lines
19 KiB
Dart
652 lines
19 KiB
Dart
// Copyright (c) 2022, 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.
|
|
|
|
/// # Benchmark for iterators of common collections.
|
|
///
|
|
/// The purpose of this benchmark is to detect performance changes in the
|
|
/// iterators for common collections (system Lists, Maps, etc).
|
|
///
|
|
/// ## Polymorphic benchmarks
|
|
///
|
|
/// Benchmark names beginning with `Iterators.poly.`.
|
|
///
|
|
/// These benchmark use the iterators from a common polymorphic for-in loop, so
|
|
/// none of the methods involved in iterating are inlined. This gives an
|
|
/// indication of worst-case performance.
|
|
///
|
|
/// Iterables of different sizes (small (N=1) and large (N=100)) are used to
|
|
/// give insight into the fixed vs per-element costs.
|
|
///
|
|
/// Results are normalized by iterating 1000 elements and reporting the time per
|
|
/// element. There is an outer loop that calls the iterator loop in `sinkAll`.
|
|
///
|
|
/// The dispatched (polymorphic) calls are to `get:iterator`, `moveNext` and
|
|
/// `get:current`.
|
|
///
|
|
/// | N | outer | `get:iterator` | `moveNext` | `get:current` |
|
|
/// | ---: | ----: | -------------: | ---------: | ------------: |
|
|
/// | 0* | 1000 | 1000 | 1000 | 0 |
|
|
/// | 1 | 1000 | 1000 | 2000 | 1000 |
|
|
/// | 2* | 500 | 500 | 1500 | 1000 |
|
|
/// | 100 | 10 | 10 | 1010 | 1000 |
|
|
///
|
|
/// * By default only the N=1 and N=100 benchmarks arer run. The N=0 and N=2
|
|
/// series are available running manually with `--0` and `--2` command-line
|
|
/// arguments.
|
|
///
|
|
/// Generic Iterables have benchmarks for different element types. There are
|
|
/// benchmarks for `int` type arguments, which have a fast type test, and for
|
|
/// `Thing<Iterable<Comparable>>`, which is harder to test quickly. These tests
|
|
/// are distinguished by `int` and `Hard` in the name.
|
|
///
|
|
/// ## Monomorphic benchmarks
|
|
///
|
|
/// Benchmark names beginning with `Iterators.mono.`.
|
|
///
|
|
/// A subset of the polymorphic benchmarks are also implemented with a
|
|
/// per-benchmark for-in loop directly iterating a collection of known
|
|
/// representation. This gives the compiler the opportunity to inline the
|
|
/// methods into the loop and represents the best-case performance.
|
|
///
|
|
/// ## Example benchmarks
|
|
///
|
|
/// The name has 4-7 words separated by periods. The first word is always
|
|
/// 'Iterators', and the second is either 'mono' for monomorphic loops, or
|
|
/// 'poly' for benchmarks using a shared polymorphic loop. The last word is a
|
|
/// number which is the size (length) of the Iterable.
|
|
///
|
|
/// ### Iterators.mono.const.Map.int.values.100
|
|
///
|
|
/// A for-in loop over the values iterable of a known constant Map with value
|
|
/// type `int` and 100 entries.
|
|
///
|
|
/// ### Iterators.poly.Runes.1
|
|
///
|
|
/// An iteration over the String.runes iterable of a single character String
|
|
/// using the shared polymorphic loop.
|
|
///
|
|
/// ### Iterators.poly.HashMap.Hard.keys.100
|
|
///
|
|
/// An iteration of over the keys iterable of a HashMap with key type
|
|
/// `Thing<Iterable<Comparable>>` and 100 entries.
|
|
///
|
|
/// ### Iterators.*.UpTo.*
|
|
///
|
|
/// The UpTo iterable is a minimal iterable that provides successive
|
|
/// numbers. The `moveNext` and `get:current` methods are small. Comparing
|
|
/// Iterators.poly.UpTo.*.100 to Iterators.poly.*.100 gives an indication of how
|
|
/// much work is done by `moveNext` (and sometimes `get:current`).
|
|
///
|
|
/// ### Iterators.mono.Nothing.*
|
|
///
|
|
/// The Nothing benchmark has no iteration over an iterable and is used to get a
|
|
/// baseline time for running the benchmark loop for monomorphic
|
|
/// benchmarks. This can be a substantial fraction of
|
|
///
|
|
/// Consider the times
|
|
///
|
|
/// Iterators.mono.CodeUnits.1 = 7.0ns
|
|
/// Iterators.mono.Nothing.1 = 3.1ns
|
|
///
|
|
/// Because the trip count (i.e. 1) of the for-in loop is so small, there is a
|
|
/// lot of overhead attributable to the outer loop in `MonoBenchmark.run`. The
|
|
/// 1000/1 = 1000 trips of outer loops takes 3.1us (3.1ns * 1000 trips), so
|
|
/// CodeUnits is spending only 7.0-3.1 = 3.9ns per character in the for-in
|
|
/// loop over the `.codeUnits` of the single-character String.
|
|
///
|
|
/// Iterators.mono.CodeUnits.100 = 1.83ns
|
|
/// Iterators.mono.Nothing.100 = 0.05ns
|
|
///
|
|
/// Now the outer loop runs only 1000/100 = 10 times, for 0.05us. If we subtract
|
|
/// this from 1.83, we get 1.78ns per character for long strings.
|
|
///
|
|
library iterators_benchmark;
|
|
|
|
// TODO(48277): Update when fixed:
|
|
// ignore_for_file: unnecessary_lambdas
|
|
|
|
import 'dart:collection';
|
|
|
|
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
|
|
import 'data.dart';
|
|
|
|
const targetSize = 1000;
|
|
|
|
class Emitter implements ScoreEmitter {
|
|
@override
|
|
void emit(String testName, double value) {
|
|
// [value] is microseconds per ten calls to `run()`.
|
|
final nanoSeconds = value * 1000;
|
|
final singleElementTimeNs = nanoSeconds / 10 / targetSize;
|
|
print('$testName(RunTimeRaw): $singleElementTimeNs ns.');
|
|
}
|
|
}
|
|
|
|
abstract class Benchmark extends BenchmarkBase {
|
|
final int size;
|
|
bool selected = false;
|
|
Benchmark._(String name, this.size)
|
|
: super('Iterators.$name.$size', emitter: Emitter());
|
|
|
|
factory Benchmark(String name, int size, Iterable Function(int) generate) =
|
|
PolyBenchmark;
|
|
}
|
|
|
|
abstract class MonoBenchmark extends Benchmark {
|
|
final int _repeats;
|
|
MonoBenchmark(String name, int size)
|
|
: _repeats = size == 0 ? targetSize : targetSize ~/ size,
|
|
super._('mono.$name', size);
|
|
|
|
@override
|
|
void run() {
|
|
for (int i = 0; i < _repeats; i++) {
|
|
sinkMono();
|
|
}
|
|
}
|
|
|
|
void sinkMono();
|
|
}
|
|
|
|
class PolyBenchmark extends Benchmark {
|
|
final Iterable Function(int) generate;
|
|
final List<Iterable> inputs = [];
|
|
|
|
PolyBenchmark(String name, int size, this.generate)
|
|
: super._('poly.$name', size);
|
|
|
|
@override
|
|
void setup() {
|
|
if (inputs.isNotEmpty) return; // Ensure setup() is idempotent.
|
|
|
|
int totalSize = 0;
|
|
while (totalSize < targetSize) {
|
|
final sample = generate(size);
|
|
inputs.add(sample);
|
|
totalSize += size == 0 ? 1 : size;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void run() {
|
|
for (int i = 0; i < inputs.length; i++) {
|
|
sinkAll(inputs[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This function is the inner loop of the benchmark.
|
|
@pragma('dart2js:noInline')
|
|
@pragma('vm:never-inline')
|
|
void sinkAll(Iterable iterable) {
|
|
for (final value in iterable) {
|
|
sink = value;
|
|
}
|
|
}
|
|
|
|
Object? sink;
|
|
|
|
class BenchmarkConstMapIntKeys1 extends MonoBenchmark {
|
|
BenchmarkConstMapIntKeys1() : super('const.Map.int.keys', 1);
|
|
|
|
static const _map = constMapIntInt1;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.keys) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkConstMapIntKeys2 extends MonoBenchmark {
|
|
BenchmarkConstMapIntKeys2() : super('const.Map.int.keys', 2);
|
|
|
|
static const _map = constMapIntInt2;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.keys) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkConstMapIntKeys100 extends MonoBenchmark {
|
|
BenchmarkConstMapIntKeys100() : super('const.Map.int.keys', 100);
|
|
|
|
static const _map = constMapIntInt100;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.keys) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkConstMapIntValues1 extends MonoBenchmark {
|
|
BenchmarkConstMapIntValues1() : super('const.Map.int.values', 1);
|
|
|
|
static const _map = constMapIntInt1;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.values) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkConstMapIntValues2 extends MonoBenchmark {
|
|
BenchmarkConstMapIntValues2() : super('const.Map.int.values', 2);
|
|
|
|
static const _map = constMapIntInt2;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.values) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkConstMapIntValues100 extends MonoBenchmark {
|
|
BenchmarkConstMapIntValues100() : super('const.Map.int.values', 100);
|
|
|
|
static const _map = constMapIntInt100;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.values) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkMapIntKeys extends MonoBenchmark {
|
|
BenchmarkMapIntKeys(int size) : super('Map.int.keys', size) {
|
|
_map.addAll(generateMapIntInt(size));
|
|
}
|
|
|
|
final Map<int, int> _map = {};
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _map.keys) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkUpTo extends MonoBenchmark {
|
|
BenchmarkUpTo(int size) : super('UpTo', size);
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in UpTo(size)) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkNothing extends MonoBenchmark {
|
|
BenchmarkNothing(int size) : super('Nothing', size);
|
|
|
|
@override
|
|
void sinkMono() {
|
|
sink = size;
|
|
}
|
|
}
|
|
|
|
class BenchmarkCodeUnits extends MonoBenchmark {
|
|
BenchmarkCodeUnits(int size)
|
|
: string = generateString(size),
|
|
super('CodeUnits', size);
|
|
|
|
final String string;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in string.codeUnits) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkListIntGrowable extends MonoBenchmark {
|
|
BenchmarkListIntGrowable(int size)
|
|
: _list = List.generate(size, (i) => i),
|
|
super('List.int.growable', size);
|
|
|
|
final List<int> _list;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
for (final value in _list) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkListIntSystem1 extends MonoBenchmark {
|
|
// The List type here is not quite monomorphic. It is the choice between two
|
|
// 'system' Lists: a const List and a growable List. It is quite common to
|
|
// have growable and const lists at the same use-site (e.g. the const coming
|
|
// from a default argument).
|
|
//
|
|
// Ideally some combination of the class hierarchy or compiler tricks would
|
|
// ensure there is little cost of having this gentle polymorphism.
|
|
BenchmarkListIntSystem1(int size)
|
|
: _list1 = List.generate(size, (i) => i),
|
|
_list2 = generateConstListOfInt(size),
|
|
super('List.int.growable.and.const', size);
|
|
|
|
final List<int> _list1;
|
|
final List<int> _list2;
|
|
bool _flip = false;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
_flip = !_flip;
|
|
final list = _flip ? _list1 : _list2;
|
|
for (final value in list) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
class BenchmarkListIntSystem2 extends MonoBenchmark {
|
|
// The List type here is not quite monomorphic. It is the choice between two
|
|
// 'system' Lists: a const List and a fixed-length List. It is quite common to
|
|
// have fixed-length and const lists at the same use-site (e.g. the const
|
|
// coming from a default argument).
|
|
//
|
|
// Ideally some combination of the class hierarchy or compiler tricks would
|
|
// ensure there is little cost of having this gentle polymorphism.
|
|
BenchmarkListIntSystem2(int size)
|
|
: _list1 = List.generate(size, (i) => i, growable: false),
|
|
_list2 = generateConstListOfInt(size),
|
|
super('List.int.fixed.and.const', size);
|
|
|
|
final List<int> _list1;
|
|
final List<int> _list2;
|
|
bool _flip = false;
|
|
|
|
@override
|
|
void sinkMono() {
|
|
_flip = !_flip;
|
|
final list = _flip ? _list1 : _list2;
|
|
for (final value in list) {
|
|
sink = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A simple Iterable that yields the integers 0 through `length`.
|
|
///
|
|
/// This Iterable serves as the minimal interesting example to serve as a
|
|
/// baseline, and is useful in constructing other benchmark inputs.
|
|
class UpTo extends IterableBase<int> {
|
|
final int _length;
|
|
UpTo(this._length);
|
|
|
|
@override
|
|
Iterator<int> get iterator => UpToIterator(_length);
|
|
}
|
|
|
|
class UpToIterator implements Iterator<int> {
|
|
final int _length;
|
|
int _position = 0;
|
|
int? _current;
|
|
|
|
UpToIterator(this._length);
|
|
|
|
@override
|
|
int get current => _current!;
|
|
|
|
@override
|
|
bool moveNext() {
|
|
if (_position < _length) {
|
|
_current = _position++;
|
|
return true;
|
|
}
|
|
_current = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// A `Thing` has a type parameter which makes type tests in the Iterators
|
|
/// potentially harder, and equality uses the type parameter, making Iterables
|
|
/// that do lookups slower.
|
|
class Thing<T> {
|
|
static int _nextIndex = 0;
|
|
final int _index;
|
|
Thing() : _index = _nextIndex++;
|
|
|
|
@override
|
|
int get hashCode => _index;
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is Thing<T> && other._index == _index;
|
|
}
|
|
|
|
final thingGenerators = [
|
|
// TODO(48277): Use instantiated constructor tear-offs when fixed:
|
|
() => Thing<Set<String>>(),
|
|
() => Thing<Set<Duration>>(),
|
|
() => Thing<Set<BigInt>>(),
|
|
() => Thing<Queue<String>>(),
|
|
() => Thing<Queue<Duration>>(),
|
|
() => Thing<Queue<BigInt>>(),
|
|
() => Thing<List<String>>(),
|
|
() => Thing<List<Duration>>(),
|
|
() => Thing<List<BigInt>>(),
|
|
];
|
|
|
|
int _generateThingListState = 0;
|
|
List<Thing<Iterable<Comparable>>> generateThingList(int n) {
|
|
Thing nextThing(_) {
|
|
final next = (_generateThingListState++).remainder(thingGenerators.length);
|
|
return thingGenerators[next]();
|
|
}
|
|
|
|
return List.from(UpTo(n).map(nextThing));
|
|
}
|
|
|
|
Map<Thing<Iterable<Comparable>>, Thing<Iterable<Comparable>>> generateThingMap(
|
|
int n) {
|
|
return Map.fromIterables(generateThingList(n), generateThingList(n));
|
|
}
|
|
|
|
Map<Thing<Iterable<Comparable>>, Thing<Iterable<Comparable>>>
|
|
generateThingHashMap(int n) {
|
|
return HashMap.fromIterables(generateThingList(n), generateThingList(n));
|
|
}
|
|
|
|
int _generateStringState = 0;
|
|
String generateString(int n) {
|
|
return ((_generateStringState++).isEven ? 'x' : '\u2192') * n;
|
|
}
|
|
|
|
Map<int, int> generateMapIntInt(int n) =>
|
|
Map<int, int>.fromIterables(UpTo(n), UpTo(n));
|
|
|
|
Map<int, int> generateIdentityMapIntInt(int n) {
|
|
return Map<int, int>.identity()..addAll(generateMapIntInt(n));
|
|
}
|
|
|
|
/// Run the benchmark loop on various inputs to pollute type inference and JIT
|
|
/// caches.
|
|
void pollute() {
|
|
// This iterable reads `sink` mid-loop, making it infeasible for the compiler
|
|
// to move the write to `sink` out of the loop.
|
|
sinkAll(UpTo(100).map((i) {
|
|
if (i > 0 && sink != i - 1) throw StateError('sink');
|
|
return i;
|
|
}));
|
|
|
|
// TODO(sra): Do we need to add anything here? There are a lot of benchmarks,
|
|
// so that is probably sufficient to make the necessary places polymorphic.
|
|
}
|
|
|
|
/// Command-line arguments:
|
|
///
|
|
/// `--0`: Run benchmarks for empty iterables.
|
|
/// `--1`: Run benchmarks for singleton iterables.
|
|
/// `--2`: Run benchmarks for two-element iterables.
|
|
/// `--100`: Run benchmarks for 100-element iterables.
|
|
///
|
|
/// Default sizes are 1 and 100.
|
|
///
|
|
/// `--all`: Run all benchmark variants and sizes.
|
|
///
|
|
/// `foo`, `foo.bar`: a Selector.
|
|
///
|
|
/// Run benchmarks with name containing all the dot-separated words in the
|
|
/// selector, so `--Set.const` will run benchmark
|
|
/// 'Iterators.const.Set.int.N`, and `--2.UpTo` will select
|
|
/// `Iterators.UpTo.2`. Each selector is matched independently, and if
|
|
/// selectors are used, only benchmarks matching some selector are run.
|
|
///
|
|
void main(List<String> commandLineArguments) {
|
|
final arguments = [...commandLineArguments];
|
|
|
|
const allSizes = {0, 1, 2, 100};
|
|
const defaultSizes = {1, 100};
|
|
final allSizeWords = Set.unmodifiable(allSizes.map((size) => '$size'));
|
|
|
|
final Set<int> sizes = {};
|
|
final Set<String> selectors = {};
|
|
|
|
if (arguments.remove('--0')) sizes.add(0);
|
|
if (arguments.remove('--1')) sizes.add(1);
|
|
if (arguments.remove('--2')) sizes.add(2);
|
|
if (arguments.remove('--100')) sizes.add(100);
|
|
|
|
if (arguments.remove('--all')) {
|
|
sizes.addAll(allSizes);
|
|
}
|
|
|
|
selectors.addAll(arguments);
|
|
|
|
if (sizes.isEmpty) sizes.addAll(defaultSizes);
|
|
if (selectors.isEmpty) selectors.add('Iterators');
|
|
|
|
List<Benchmark> makeBenchmarksForSize(int size) {
|
|
return [
|
|
// Simple
|
|
BenchmarkNothing(size),
|
|
BenchmarkUpTo(size),
|
|
BenchmarkCodeUnits(size),
|
|
Benchmark('UpTo', size, (n) => UpTo(n)),
|
|
Benchmark('CodeUnits', size, (n) => generateString(n).codeUnits),
|
|
Benchmark('Runes', size, (n) => generateString(n).runes),
|
|
// ---
|
|
BenchmarkListIntGrowable(size),
|
|
BenchmarkListIntSystem1(size),
|
|
BenchmarkListIntSystem2(size),
|
|
Benchmark('List.int.growable', size,
|
|
(n) => List<int>.of(UpTo(n), growable: true)),
|
|
Benchmark('List.int.fixed', size,
|
|
(n) => List<int>.of(UpTo(n), growable: false)),
|
|
Benchmark('List.int.unmodifiable', size,
|
|
(n) => List<int>.unmodifiable(UpTo(n))),
|
|
// ---
|
|
Benchmark('List.Hard.growable', size, generateThingList),
|
|
// ---
|
|
Benchmark('Set.int', size, (n) => Set<int>.of(UpTo(n))),
|
|
Benchmark('const.Set.int', size, generateConstSetOfInt),
|
|
// ---
|
|
BenchmarkMapIntKeys(size),
|
|
Benchmark('Map.int.keys', size, (n) => generateMapIntInt(n).keys),
|
|
Benchmark('Map.int.values', size, (n) => generateMapIntInt(n).values),
|
|
Benchmark('Map.int.entries', size, (n) => generateMapIntInt(n).entries),
|
|
// ---
|
|
Benchmark('Map.identity.int.keys', size,
|
|
(n) => generateIdentityMapIntInt(n).keys),
|
|
Benchmark('Map.identity.int.values', size,
|
|
(n) => generateIdentityMapIntInt(n).values),
|
|
Benchmark('Map.identity.int.entries', size,
|
|
(n) => generateIdentityMapIntInt(n).entries),
|
|
// ---
|
|
Benchmark(
|
|
'const.Map.int.keys', size, (n) => generateConstMapIntInt(n).keys),
|
|
Benchmark('const.Map.int.values', size,
|
|
(n) => generateConstMapIntInt(n).values),
|
|
Benchmark('const.Map.int.entries', size,
|
|
(n) => generateConstMapIntInt(n).entries),
|
|
// ---
|
|
Benchmark('Map.Hard.keys', size, (n) => generateThingMap(n).keys),
|
|
Benchmark('Map.Hard.values', size, (n) => generateThingMap(n).values),
|
|
// ---
|
|
Benchmark('HashMap.int.keys', size,
|
|
(n) => HashMap<int, int>.fromIterables(UpTo(n), UpTo(n)).keys),
|
|
Benchmark('HashMap.int.values', size,
|
|
(n) => HashMap<int, int>.fromIterables(UpTo(n), UpTo(n)).values),
|
|
Benchmark('HashMap.int.entries', size,
|
|
(n) => HashMap<int, int>.fromIterables(UpTo(n), UpTo(n)).entries),
|
|
// ---
|
|
Benchmark('HashMap.Hard.keys', size, (n) => generateThingHashMap(n).keys),
|
|
Benchmark(
|
|
'HashMap.Hard.values', size, (n) => generateThingHashMap(n).values),
|
|
];
|
|
}
|
|
|
|
final benchmarks = [
|
|
BenchmarkConstMapIntKeys1(),
|
|
BenchmarkConstMapIntKeys2(),
|
|
BenchmarkConstMapIntKeys100(),
|
|
BenchmarkConstMapIntValues1(),
|
|
BenchmarkConstMapIntValues2(),
|
|
BenchmarkConstMapIntValues100(),
|
|
for (final size in allSizes) ...makeBenchmarksForSize(size),
|
|
];
|
|
|
|
// Select benchmarks
|
|
final unusedSelectors = {...selectors};
|
|
for (final benchmark in benchmarks) {
|
|
final nameWords = benchmark.name.split('.').toSet();
|
|
for (final selector in selectors) {
|
|
final selectorWords = selector.split('.').toSet();
|
|
if (nameWords.containsAll(selectorWords)) {
|
|
unusedSelectors.remove(selector);
|
|
if (selectorWords.any(allSizeWords.contains) ||
|
|
sizes.contains(benchmark.size)) {
|
|
benchmark.selected = true;
|
|
}
|
|
// continue matching to remove other matching selectors.
|
|
}
|
|
}
|
|
}
|
|
if (unusedSelectors.isNotEmpty) {
|
|
throw ArgumentError(unusedSelectors, 'selectors match no benchmark');
|
|
}
|
|
|
|
// Warmup all benchmarks to ensure JIT compilers see full polymorphism.
|
|
for (var benchmark in benchmarks) {
|
|
pollute();
|
|
benchmark.setup();
|
|
}
|
|
|
|
// Warm up all the benchmarks, including the non-selected ones.
|
|
for (int i = 0; i < 10; i++) {
|
|
for (var benchmark in benchmarks) {
|
|
pollute();
|
|
final marker = Object();
|
|
sink = marker;
|
|
benchmark.warmup();
|
|
if (benchmark.size > 0 && identical(sink, marker)) throw 'unexpected';
|
|
}
|
|
}
|
|
|
|
for (var benchmark in benchmarks) {
|
|
// `report` calls `setup`, but `setup` is idempotent.
|
|
if (benchmark.selected) {
|
|
benchmark.report();
|
|
}
|
|
}
|
|
}
|