blob: 282ef03b47ec6735e16577c50b00b8970a3d44b7 [file] [log] [blame]
// Copyright (c) 2011, 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.
part of benchmark_lib;
/** Accessors for our Singleton variables. */
BenchmarkSuite get BENCHMARK_SUITE {
if (BenchmarkSuite._ONLY == null) {
BenchmarkSuite._ONLY = new BenchmarkSuite._internal();
}
return BenchmarkSuite._ONLY;
}
BenchmarkView get BENCHMARK_VIEW {
if (BenchmarkView._ONLY == null) {
BenchmarkView._ONLY = new BenchmarkView._internal();
}
return BenchmarkView._ONLY;
}
/** The superclass from which all benchmarks inherit from. */
class BenchmarkBase {
/** Benchmark name. */
final String name;
const BenchmarkBase(String name) : this.name = name;
/**
* The benchmark code.
* This function is not used, if both [warmup] and [exercise] are overwritten.
*/
void run() {}
/** Runs a short version of the benchmark. By default invokes [run] once. */
void warmup() {
run();
}
/** Exercices the benchmark. By default invokes [run] 10 times. */
void exercise() {
for (int i = 0; i < 10; i++) {
run();
}
}
/** Not measured setup code executed prior to the benchmark runs. */
void setup() {}
/** Not measures teardown code executed after the benchark runs. */
void teardown() {}
/**
* Measures the score for this benchmark by executing it repeately until
* time minimum has been reached.
*/
static double measureFor(Function f, int timeMinimum) {
int time = 0;
int iter = 0;
Stopwatch watch = new Stopwatch();
watch.start();
int elapsed = 0;
while (elapsed < timeMinimum || iter < 32) {
f();
elapsed = watch.elapsedMilliseconds;
iter++;
}
return (1000.0 * iter) / elapsed;
}
/**
* Measures the score for the benchmark and returns it.
* We measure iterations / sec (so bigger = better!).
*/
double measure() {
setup();
// Warmup for at least 1000ms. Discard result.
measureFor(() {
this.warmup();
}, 1000);
// Run the benchmark for at least 1000ms.
double result = measureFor(() {
this.exercise();
}, 1000);
teardown();
return result;
}
void report() {
num score = measure();
Map<String, int> normalizingDict = {'Smoketest': 100};
score = score / normalizingDict[name];
BENCHMARK_SUITE.updateIndividualScore(name, score);
}
}
/** The controller class that runs all of the benchmarks. */
class BenchmarkSuite {
/** The set of benchmarks that have yet to run. */
List<Function> benchmarks;
/**
* The set of scores from the benchmarks that have already run. (Used for
* calculating the Geometric mean).
*/
List<num> scores;
/** The total number of benchmarks we will be running. */
int totalBenchmarks;
/** Singleton pattern: There's only one BenchmarkSuite. */
static BenchmarkSuite _ONLY = null;
BenchmarkSuite._internal() {
scores = [];
benchmarks = [() => Smoketest.main()];
totalBenchmarks = benchmarks.length;
}
/** Run all of the benchmarks that we have in our benchmarks list. */
runBenchmarks() {
runBenchmarksHelper(benchmarks);
}
/**
* Run the remaining benchmarks in our list. We chain the calls providing
* little breaks for the main page to gain control, so we don't force the
* entire page to hang the whole time.
*/
runBenchmarksHelper(List<Function> remainingBenchmarks) {
// Remove the last benchmark, and run it.
var benchmark = remainingBenchmarks.removeLast();
benchmark();
if (remainingBenchmarks.length > 0) {
/* Provide small breaks between each benchmark, so that the browser
doesn't get unhappy about long running scripts, and so the user
can regain control of the UI to kill the page as needed. */
new Timer(const Duration(milliseconds: 25),
() => runBenchmarksHelper(remainingBenchmarks));
} else if (remainingBenchmarks.length == 0) {
// We've run all of the benchmarks. Update the page with the score.
BENCHMARK_VIEW.setScore(geometricMean(scores));
}
}
/** Store the results of a single benchmark run. */
updateIndividualScore(String name, num score) {
scores.add(score);
BENCHMARK_VIEW.incrementProgress(name, score, totalBenchmarks);
}
/** Computes the geometric mean of a set of numbers. */
geometricMean(numbers) {
num log = 0;
for (num n in numbers) {
log += Math.log(n);
}
return Math.pow(Math.E, log / numbers.length);
}
}
/** Controls how results are displayed to the user, by updating the HTML. */
class BenchmarkView {
/** The number of benchmarks that have finished executing. */
int numCompleted = 0;
/** Singleton pattern: There's only one BenchmarkSuite. */
static BenchmarkView _ONLY = null;
BenchmarkView._internal();
/** Update the page HTML to show the calculated score. */
setScore(num score) {
String newScore = formatScore(score * 100.0);
Element body = document.queryAll("body")[0];
body.nodes
.add(new Element.html("<p id='testResultScore'>Score: $newScore</p>"));
}
/**
* Update the page HTML to show how much progress we've made through the
* benchmarks.
*/
incrementProgress(String name, num score, num totalBenchmarks) {
String newScore = formatScore(score * 100.0);
numCompleted++;
// Slightly incorrect (truncating) percentage, but this is just to show
// the user we're making progress.
num percentage = 100 * numCompleted ~/ totalBenchmarks;
}
/**
* Rounds the score to have at least three significant digits (hopefully)
* helping readability of the scores.
*/
String formatScore(num value) {
if (value > 100) {
return value.toStringAsFixed(0);
} else {
return value.toStringAsFixed(2);
}
}
}