Skip to content

Commit 521495f

Browse files
committed
Make benchmarking suite give more stable results.
Address the following issues: - Dead Code Elimination (DCE) and Escape Analysis - Type Profile Pollution (Megamorphic Inline Caches) - Insufficient Warmup Duration
1 parent a4d82b7 commit 521495f

6 files changed

Lines changed: 90 additions & 25 deletions

File tree

bin/benchmark/suites/json_benchmark.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,27 @@ void runJson(String name, String input) => run(
4242
throw StateError('Parsers provide inconsistent results');
4343
}
4444
},
45-
parse: () => jsonParser.parse(input),
46-
accept: () => jsonParser.accept(input),
47-
native: () => convert.json.decode(input),
45+
parse: (count) {
46+
var result = 0;
47+
for (var c = 0; c < count; c++) {
48+
result ^= jsonParser.parse(input).hashCode;
49+
}
50+
return result;
51+
},
52+
accept: (count) {
53+
var result = 0;
54+
for (var c = 0; c < count; c++) {
55+
result ^= jsonParser.accept(input).hashCode;
56+
}
57+
return result;
58+
},
59+
native: (count) {
60+
var result = 0;
61+
for (var c = 0; c < count; c++) {
62+
result ^= convert.json.decode(input).hashCode;
63+
}
64+
return result;
65+
},
4866
);
4967

5068
void main() {

bin/benchmark/suites/regexp_benchmark.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,27 @@ void runRegExp(String regexp, Parser<void> parser, String input) {
2525
throw StateError('Expressions provide inconsistent results');
2626
}
2727
},
28-
parse: () => parserPattern.allMatches(input).toList(),
29-
accept: () => parserPattern.allMatches(input).isNotEmpty,
30-
native: () => nativePattern.allMatches(input).toList(),
28+
parse: (count) {
29+
var result = 0;
30+
for (var c = 0; c < count; c++) {
31+
result ^= parserPattern.allMatches(input).hashCode;
32+
}
33+
return result;
34+
},
35+
accept: (count) {
36+
var result = 0;
37+
for (var c = 0; c < count; c++) {
38+
result ^= parserPattern.allMatches(input).hashCode;
39+
}
40+
return result;
41+
},
42+
native: (count) {
43+
var result = 0;
44+
for (var c = 0; c < count; c++) {
45+
result ^= nativePattern.allMatches(input).hashCode;
46+
}
47+
return result;
48+
},
3149
);
3250
}
3351

bin/benchmark/utils/benchmark.dart

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:data/stats.dart';
22

33
/// Function type to be benchmarked.
4-
typedef Benchmark = void Function();
4+
typedef Benchmark = int Function(int count);
55

66
/// Measures the time it takes to run [function] in microseconds.
77
///
@@ -11,6 +11,7 @@ Jackknife<double> benchmark(
1111
Benchmark function, {
1212
int minLoop = 25,
1313
Duration minDuration = const Duration(milliseconds: 100),
14+
Duration warmupDuration = const Duration(seconds: 1),
1415
int sampleCount = 25,
1516
double confidenceLevel = 0.95,
1617
}) {
@@ -25,6 +26,11 @@ Jackknife<double> benchmark(
2526
'in $minDuration.',
2627
);
2728
}
29+
// Warmup phase.
30+
final warmupWatch = Stopwatch()..start();
31+
while (warmupWatch.elapsed < warmupDuration) {
32+
_benchmark(function, count);
33+
}
2834
// Collect samples.
2935
final samples = <double>[];
3036
for (var i = 0; i < sampleCount; i++) {
@@ -37,15 +43,16 @@ Jackknife<double> benchmark(
3743
);
3844
}
3945

46+
/// Blackhole variable to prevent dead code elimination.
47+
int blackhole = 0;
48+
4049
@pragma('vm:never-inline')
4150
@pragma('vm:unsafe:no-interrupts')
4251
Duration _benchmark(Benchmark function, int count) {
4352
final watch = Stopwatch();
4453
watch.start();
45-
var n = count + 0;
46-
while (n-- > 0) {
47-
function();
48-
}
54+
final result = function(count);
4955
watch.stop();
56+
blackhole ^= result;
5057
return watch.elapsed;
5158
}

bin/benchmark/utils/runner.dart

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ final defaultCharsInput = [
4040
].shuffled(Random(42));
4141
final defaultStringInput = defaultCharsInput.join();
4242

43-
final List<({String name, Benchmark benchmark})> _benchmarkEntries = (() {
43+
final List<({String name, void Function() benchmark})> _benchmarkEntries = (() {
4444
Future.delayed(const Duration(milliseconds: 1)).then((_) {
4545
for (final (:name, :benchmark) in _benchmarkEntries) {
4646
if (optionFilter == null || optionFilter == name) benchmark();
4747
}
4848
});
49-
return SortedList<({String name, Benchmark benchmark})>(
49+
return SortedList<({String name, void Function() benchmark})>(
5050
comparator: compareAsciiLowerCase.keyOf((entry) => entry.name),
5151
);
5252
})();
@@ -56,7 +56,7 @@ final numberPrinter = FixedNumberPrinter(precision: 3);
5656
/// Generic benchmark runner.
5757
void run(
5858
String name, {
59-
required Benchmark verify,
59+
required void Function() verify,
6060
required Benchmark parse,
6161
required Benchmark accept,
6262
Benchmark? native,
@@ -83,7 +83,7 @@ void run(
8383
final benchmarks = [
8484
benchmark(parse),
8585
benchmark(accept),
86-
native?.also(benchmark),
86+
if (native != null) benchmark(native),
8787
].whereType<Jackknife<double>>();
8888
for (final benchmark in benchmarks) {
8989
stdout.write(optionSeparator);
@@ -133,15 +133,23 @@ void runChars(String name, Parser<void> parser, {int? success, String? input}) {
133133
throw StateError('Expected $success_ successes, but got $count');
134134
}
135135
},
136-
parse: () {
137-
for (var i = 0; i < inputLength; i++) {
138-
parser.parse(input_, start: i);
136+
parse: (count) {
137+
var result = 0;
138+
for (var c = 0; c < count; c++) {
139+
for (var i = 0; i < inputLength; i++) {
140+
result ^= parser.parse(input_, start: i).hashCode;
141+
}
139142
}
143+
return result;
140144
},
141-
accept: () {
142-
for (var i = 0; i < inputLength; i++) {
143-
parser.accept(input_, start: i);
145+
accept: (count) {
146+
var result = 0;
147+
for (var c = 0; c < count; c++) {
148+
for (var i = 0; i < inputLength; i++) {
149+
result ^= parser.accept(input_, start: i).hashCode;
150+
}
144151
}
152+
return result;
145153
},
146154
);
147155
}
@@ -172,7 +180,19 @@ void runString(
172180
);
173181
}
174182
},
175-
parse: () => parser.parse(input_),
176-
accept: () => parser.accept(input_),
183+
parse: (count) {
184+
var result = 0;
185+
for (var c = 0; c < count; c++) {
186+
result ^= parser.parse(input_).hashCode;
187+
}
188+
return result;
189+
},
190+
accept: (count) {
191+
var result = 0;
192+
for (var c = 0; c < count; c++) {
193+
result ^= parser.accept(input_) ? 1 : 0;
194+
}
195+
return result;
196+
},
177197
);
178198
}

bin/lisp/lisp.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void main(List<String> arguments) {
4040
} else if (option == '-i') {
4141
interactiveMode = true;
4242
} else if (option == '-?') {
43-
stdout.writeln('${Platform.executable} smalltalk.dart -n -i [files]');
43+
stdout.writeln('${Platform.executable} lisp.dart -n -i [files]');
4444
stdout.writeln(' -i enforces the interactive mode');
4545
stdout.writeln(' -n does not load the standard library');
4646
exit(0);

pubspec.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ dev_dependencies:
3131
test: ^1.26.0
3232
dependency_overrides:
3333
xml:
34-
git: https://github.com/renggli/dart-xml.git
34+
git: https://github.com/renggli/dart-xml.git
35+
petitparser:
36+
git: https://github.com/petitparser/dart-petitparser.git

0 commit comments

Comments
 (0)