Unverified Commit 676edd4c authored by gaaclarke's avatar gaaclarke Committed by GitHub

Added the ability for microbenchmarks to print the probability of the result. (#101154)

parent bafac177
...@@ -3,6 +3,50 @@ ...@@ -3,6 +3,50 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert' show json; import 'dart:convert' show json;
import 'dart:math' as math;
double _doNormal(
{required double mean, required double stddev, required double x}) {
return (1.0 / (stddev * math.sqrt(2.0 * math.pi))) *
math.pow(math.e, -0.5 * math.pow((x - mean) / stddev, 2.0));
}
double _doMean(List<double> values) =>
values.reduce((double x, double y) => x + y) / values.length;
double _doStddev(List<double> values, double mean) {
double stddev = 0.0;
for (final double value in values) {
stddev += (value - mean) * (value - mean);
}
return math.sqrt(stddev / values.length);
}
double _doIntegral({
required double Function(double) func,
required double start,
required double stop,
required double resolution,
}) {
double result = 0.0;
while (start < stop) {
final double value = func(start);
result += resolution * value;
start += resolution;
}
return result;
}
/// Probability is defined as the probability that the mean is within the
/// [margin] of the true value.
double _doProbability({required double mean, required double stddev, required double margin}) {
return _doIntegral(
func: (double x) => _doNormal(mean: mean, stddev: stddev, x: x),
start: (1.0 - margin) * mean,
stop: (1.0 + margin) * mean,
resolution: 0.001,
);
}
/// This class knows how to format benchmark results for machine and human /// This class knows how to format benchmark results for machine and human
/// consumption. /// consumption.
...@@ -33,6 +77,28 @@ class BenchmarkResultPrinter { ...@@ -33,6 +77,28 @@ class BenchmarkResultPrinter {
_results.add(_BenchmarkResult(description, value, unit, name)); _results.add(_BenchmarkResult(description, value, unit, name));
} }
/// Adds a benchmark result to the list of results and a probability of that
/// result.
///
/// The probability is calculated as the probability that the mean is +- 5% of
/// the true value.
///
/// See also [addResult].
void addResultStatistics({
required String description,
required List<double> values,
required String unit,
required String name,
}) {
final double mean = _doMean(values);
final double stddev = _doStddev(values, mean);
const double margin = 0.05;
final double probability = _doProbability(mean: mean, stddev: stddev, margin: margin);
_results.add(_BenchmarkResult(description, mean, unit, name));
_results.add(_BenchmarkResult('$description - probability margin of error $margin', probability,
'percent', '${name}_probability_5pct'));
}
/// Prints the results added via [addResult] to standard output, once as JSON /// Prints the results added via [addResult] to standard output, once as JSON
/// for computer consumption and once formatted as plain text for humans. /// for computer consumption and once formatted as plain text for humans.
void printToStdout() { void printToStdout() {
......
...@@ -21,6 +21,7 @@ Future<void> main() async { ...@@ -21,6 +21,7 @@ Future<void> main() async {
final Stopwatch watch = Stopwatch(); final Stopwatch watch = Stopwatch();
int iterations = 0; int iterations = 0;
final List<double> values = <double>[];
await benchmarkWidgets((WidgetTester tester) async { await benchmarkWidgets((WidgetTester tester) async {
stocks.main(); stocks.main();
...@@ -33,8 +34,10 @@ Future<void> main() async { ...@@ -33,8 +34,10 @@ Future<void> main() async {
final Element appState = tester.element(find.byType(stocks.StocksApp)); final Element appState = tester.element(find.byType(stocks.StocksApp));
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmark; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmark;
Duration elapsed = Duration.zero;
while (elapsed < kBenchmarkTime) {
watch.reset();
watch.start(); watch.start();
while (watch.elapsed < kBenchmarkTime) {
appState.markNeedsBuild(); appState.markNeedsBuild();
// We don't use tester.pump() because we're trying to drive it in an // We don't use tester.pump() because we're trying to drive it in an
// artificially high load to find out how much CPU each frame takes. // artificially high load to find out how much CPU each frame takes.
...@@ -43,15 +46,17 @@ Future<void> main() async { ...@@ -43,15 +46,17 @@ Future<void> main() async {
// We use Timer.run to ensure there's a microtask flush in between // We use Timer.run to ensure there's a microtask flush in between
// the two calls below. // the two calls below.
await tester.pumpBenchmark(Duration(milliseconds: iterations * 16)); await tester.pumpBenchmark(Duration(milliseconds: iterations * 16));
watch.stop();
iterations += 1; iterations += 1;
elapsed += Duration(microseconds: watch.elapsedMicroseconds);
values.add(watch.elapsedMicroseconds.toDouble());
} }
watch.stop();
}); });
final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
printer.addResult( printer.addResultStatistics(
description: 'Stock build', description: 'Stock build',
value: watch.elapsedMicroseconds / iterations, values: values,
unit: 'µs per iteration', unit: 'µs per iteration',
name: 'stock_build_iteration', name: 'stock_build_iteration',
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment