Bug 1481037 - Update StyleBench. r=jmaher

Pick upstream changes.

Differential Revision: https://phabricator.services.mozilla.com/D2757

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Emilio Cobos Álvarez 2018-08-07 15:31:47 +00:00
parent 6480962188
commit 0f460b1379
13 changed files with 1310 additions and 65 deletions

View File

@ -378,13 +378,43 @@ class Output(object):
"""
correctionFactor = 3
results = [i for i, j in val_list]
# stylebench has 4 tests, each of these are made of up 12 subtests
# and a sum of the 12 values. We receive 52 values, and want to use
# the 4 test values, not the sub test values.
if len(results) != 52:
raise Exception("StyleBench has 52 subtests, found: %s instead" % len(results))
results = results[12::13]
# stylebench has 5 tests, each of these are made of up 5 subtests
#
# * Adding classes.
# * Removing classes.
# * Mutating attributes.
# * Adding leaf elements.
# * Removing leaf elements.
#
# which are made of two subtests each (sync/async) and repeated 5 times
# each, thus, the list here looks like:
#
# [Test name/Adding classes - 0/ Sync; <x>]
# [Test name/Adding classes - 0/ Async; <y>]
# [Test name/Adding classes - 0; <x> + <y>]
# [Test name/Removing classes - 0/ Sync; <x>]
# [Test name/Removing classes - 0/ Async; <y>]
# [Test name/Removing classes - 0; <x> + <y>]
# ...
# [Test name/Adding classes - 1 / Sync; <x>]
# [Test name/Adding classes - 1 / Async; <y>]
# [Test name/Adding classes - 1 ; <x> + <y>]
# ...
# [Test name/Removing leaf elements - 4; <x> + <y>]
# [Test name; <sum>] <- This is what we want.
#
# So, 5 (subtests) *
# 5 (repetitions) *
# 3 (entries per repetition (sync/async/sum)) =
# 75 entries for test before the sum.
#
# We receive 76 entries per test, which ads up to 380. We want to use
# the 5 test entries, not the rest.
if len(results) != 380:
raise Exception("StyleBench has 380 entries, found: %s instead" % len(results))
results = results[75::76]
score = 60 * 1000 / filter.geometric_mean(results) / correctionFactor
return score

View File

@ -270,13 +270,43 @@ class Output(object):
"""
correctionFactor = 3
results = [i for i, j in val_list]
# stylebench has 4 tests, each of these are made of up 12 subtests
# and a sum of the 12 values. We receive 52 values, and want to use
# the 4 test values, not the sub test values.
if len(results) != 52:
raise Exception("StyleBench has 52 subtests, found: %s instead" % len(results))
results = results[12::13]
# stylebench has 5 tests, each of these are made of up 5 subtests
#
# * Adding classes.
# * Removing classes.
# * Mutating attributes.
# * Adding leaf elements.
# * Removing leaf elements.
#
# which are made of two subtests each (sync/async) and repeated 5 times
# each, thus, the list here looks like:
#
# [Test name/Adding classes - 0/ Sync; <x>]
# [Test name/Adding classes - 0/ Async; <y>]
# [Test name/Adding classes - 0; <x> + <y>]
# [Test name/Removing classes - 0/ Sync; <x>]
# [Test name/Removing classes - 0/ Async; <y>]
# [Test name/Removing classes - 0; <x> + <y>]
# ...
# [Test name/Adding classes - 1 / Sync; <x>]
# [Test name/Adding classes - 1 / Async; <y>]
# [Test name/Adding classes - 1 ; <x> + <y>]
# ...
# [Test name/Removing leaf elements - 4; <x> + <y>]
# [Test name; <sum>] <- This is what we want.
#
# So, 5 (subtests) *
# 5 (repetitions) *
# 3 (entries per repetition (sync/async/sum)) =
# 75 entries for test before the sum.
#
# We receive 76 entries per test, which ads up to 380. We want to use
# the 5 test entries, not the rest.
if len(results) != 380:
raise Exception("StyleBench has 380 entries, found: %s instead" % len(results))
results = results[75::76]
score = 60 * 1000 / filter.geometric_mean(results) / correctionFactor
return score

View File

@ -0,0 +1,13 @@
The source from this directory was copied from the
PerformanceTests/StyleBench directory of the Webkit repository
at: https://svn.webkit.org/repository/webkit/trunk
The SVN revision used was: 234578
The contents of this directory are intended for use to "train" the
profile guided optimization (PGO) of Firefox and for benchmarking
scenarios. The files inside this directory are not intended to ship
with Firefox or any other product. If files inside this directory
are useful for other purposes (e.g. JavaScript libraries), consumers
should vendor those files separately, as it is not appropriate to pull
in components of StyleBench for use outside of StyleBench.

View File

@ -1,20 +1,17 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>StyleBench 0.1</title>
<link rel="stylesheet" href="../Speedometer/resources/main.css">
<script src="../Speedometer/resources/main.js" defer></script>
<script src="../Speedometer/resources/benchmark-runner.js" defer></script>
<script src="../Speedometer/resources/benchmark-report.js" defer></script>
<title>StyleBench 0.3</title>
<link rel="stylesheet" href="resources/main.css">
<script src="resources/main.js" defer></script>
<script src="resources/benchmark-runner.js" defer></script>
<script src="resources/benchmark-report.js" defer></script>
<script src="../resources/statistics.js" defer></script>
<script src="resources/style-bench.js" defer></script>
<script src="resources/tests.js" defer></script>
<script>
addEventListener('load', () => {
if (!window.location.protocol.startsWith('http'))
showSection('local-message', false);
if (location.search == '?gecko' || location.search == '?raptor')
startTest();
});

View File

@ -0,0 +1,113 @@
diff --git a/third_party/webkit/PerformanceTests/StyleBench/index.html b/third_party/webkit/PerformanceTests/StyleBench/index.html
index c77554dc3506..2a561a9cbb54 100644
--- a/third_party/webkit/PerformanceTests/StyleBench/index.html
+++ b/third_party/webkit/PerformanceTests/StyleBench/index.html
@@ -5,16 +5,22 @@
<title>StyleBench 0.3</title>
<link rel="stylesheet" href="resources/main.css">
<script src="resources/main.js" defer></script>
<script src="resources/benchmark-runner.js" defer></script>
<script src="resources/benchmark-report.js" defer></script>
<script src="../resources/statistics.js" defer></script>
<script src="resources/style-bench.js" defer></script>
<script src="resources/tests.js" defer></script>
+ <script>
+ addEventListener('load', () => {
+ if (location.search == '?gecko' || location.search == '?raptor')
+ startTest();
+ });
+ </script>
</head>
<body>
<main>
<a id="logo-link" href="javascript:showHome()"></a>
<section id="home" class="selected">
<p>
StyleBench is a browser benchmark that measures the performance of the style resolution mechanism.
diff --git a/third_party/webkit/PerformanceTests/StyleBench/resources/benchmark-report.js b/third_party/webkit/PerformanceTests/StyleBench/resources/benchmark-report.js
index b33021d9d9ce..58b3e46982d1 100644
--- a/third_party/webkit/PerformanceTests/StyleBench/resources/benchmark-report.js
+++ b/third_party/webkit/PerformanceTests/StyleBench/resources/benchmark-report.js
@@ -1,12 +1,13 @@
// This file can be customized to report results as needed.
(function () {
- if (!window.testRunner && location.search != '?webkit' && location.hash != '#webkit')
+ if (!window.testRunner && location.search != '?webkit' && location.hash != '#webkit' &&
+ location.search != '?gecko' && location.search != '?raptor')
return;
if (window.testRunner)
testRunner.waitUntilDone();
var scriptElement = document.createElement('script');
scriptElement.src = '../resources/runner.js';
document.head.appendChild(scriptElement);
@@ -31,20 +32,22 @@
customIterationCount: iterationCount,
doNotIgnoreInitialRun: true,
doNotMeasureMemoryUsage: true,
continueTesting: !isLastTest,
unit: unit,
name: name,
aggregator: aggregator};
}
- PerfTestRunner.prepareToMeasureValuesAsync(createTest(null, 'Geometric'));
+ if (window.PerfTestRunner)
+ PerfTestRunner.prepareToMeasureValuesAsync(createTest(null, 'Geometric'));
},
didRunSuites: function (measuredValues) {
- PerfTestRunner.measureValueAsync(measuredValues.geomean);
+ if (window.PerfTestRunner)
+ PerfTestRunner.measureValueAsync(measuredValues.geomean);
valuesByIteration.push(measuredValues);
},
didFinishLastIteration: function () {
document.head.removeChild(document.querySelector('style'));
var measuredValuesByFullName = {};
function addToMeasuredValue(value, fullName, aggregator) {
var values = measuredValuesByFullName[fullName] || new Array;
@@ -63,21 +66,37 @@
for (var subtestName in test.tests)
addToMeasuredValue(test.tests[subtestName], suiteName + '/' + testName + '/' + subtestName);
addToMeasuredValue(test.total, suiteName + '/' + testName, 'Total');
}
addToMeasuredValue(suite.total, suiteName, 'Total');
}
});
- PerfTestRunner.reportValues(createTest(null, null, false, 'pt'), scores);
+ if (window.PerfTestRunner)
+ PerfTestRunner.reportValues(createTest(null, null, false, 'pt'), scores);
var fullNames = new Array;
for (var fullName in measuredValuesByFullName)
fullNames.push(fullName);
- for (var i = 0; i < fullNames.length; i++) {
- var values = measuredValuesByFullName[fullNames[i]];
- PerfTestRunner.reportValues(createTest(fullNames[i], values.aggregator, i + 1 == fullNames.length), values);
+ if (location.search == '?raptor') {
+ var data = ['raptor-benchmark', 'speedometer', measuredValuesByFullName];
+ window.postMessage(data, '*');
+ } else if (typeof tpRecordTime !== "undefined") {
+ var values = new Array;
+ var allNames = new Array;
+ for (var i = 0; i < fullNames.length; i++) {
+ var vals = measuredValuesByFullName[fullNames[i]];
+ values.push(vals);
+ for (var count = 0; count < vals.length; count ++)
+ allNames.push(fullNames[i]);
+ }
+ tpRecordTime(values.join(','), 0, allNames.join(','));
+ } else if (window.PerfTestRunner) {
+ for (var i = 0; i < fullNames.length; i++) {
+ var values = measuredValuesByFullName[fullNames[i]];
+ PerfTestRunner.reportValues(createTest(fullNames[i], values.aggregator, i + 1 == fullNames.length), values);
+ }
}
}
};
})();

View File

@ -0,0 +1,102 @@
// This file can be customized to report results as needed.
(function () {
if (!window.testRunner && location.search != '?webkit' && location.hash != '#webkit' &&
location.search != '?gecko' && location.search != '?raptor')
return;
if (window.testRunner)
testRunner.waitUntilDone();
var scriptElement = document.createElement('script');
scriptElement.src = '../resources/runner.js';
document.head.appendChild(scriptElement);
var styleElement = document.createElement('style');
styleElement.textContent = 'pre { padding-top: 600px; }';
document.head.appendChild(styleElement);
var createTest;
var valuesByIteration = new Array;
window.onload = function () {
document.body.removeChild(document.querySelector('main'));
startBenchmark();
}
window.benchmarkClient = {
iterationCount: 5, // Use 4 different instances of DRT/WTR to run 5 iterations.
willStartFirstIteration: function (iterationCount) {
createTest = function (name, aggregator, isLastTest, unit = 'ms') {
return {
customIterationCount: iterationCount,
doNotIgnoreInitialRun: true,
doNotMeasureMemoryUsage: true,
continueTesting: !isLastTest,
unit: unit,
name: name,
aggregator: aggregator};
}
if (window.PerfTestRunner)
PerfTestRunner.prepareToMeasureValuesAsync(createTest(null, 'Geometric'));
},
didRunSuites: function (measuredValues) {
if (window.PerfTestRunner)
PerfTestRunner.measureValueAsync(measuredValues.geomean);
valuesByIteration.push(measuredValues);
},
didFinishLastIteration: function () {
document.head.removeChild(document.querySelector('style'));
var measuredValuesByFullName = {};
function addToMeasuredValue(value, fullName, aggregator) {
var values = measuredValuesByFullName[fullName] || new Array;
measuredValuesByFullName[fullName] = values;
values.push(value);
values.aggregator = aggregator;
}
var scores = [];
valuesByIteration.forEach(function (measuredValues) {
scores.push(measuredValues.score);
for (var suiteName in measuredValues.tests) {
var suite = measuredValues.tests[suiteName];
for (var testName in suite.tests) {
var test = suite.tests[testName];
for (var subtestName in test.tests)
addToMeasuredValue(test.tests[subtestName], suiteName + '/' + testName + '/' + subtestName);
addToMeasuredValue(test.total, suiteName + '/' + testName, 'Total');
}
addToMeasuredValue(suite.total, suiteName, 'Total');
}
});
if (window.PerfTestRunner)
PerfTestRunner.reportValues(createTest(null, null, false, 'pt'), scores);
var fullNames = new Array;
for (var fullName in measuredValuesByFullName)
fullNames.push(fullName);
if (location.search == '?raptor') {
var data = ['raptor-benchmark', 'speedometer', measuredValuesByFullName];
window.postMessage(data, '*');
} else if (typeof tpRecordTime !== "undefined") {
var values = new Array;
var allNames = new Array;
for (var i = 0; i < fullNames.length; i++) {
var vals = measuredValuesByFullName[fullNames[i]];
values.push(vals);
for (var count = 0; count < vals.length; count ++)
allNames.push(fullNames[i]);
}
tpRecordTime(values.join(','), 0, allNames.join(','));
} else if (window.PerfTestRunner) {
for (var i = 0; i < fullNames.length; i++) {
var values = measuredValuesByFullName[fullNames[i]];
PerfTestRunner.reportValues(createTest(fullNames[i], values.aggregator, i + 1 == fullNames.length), values);
}
}
}
};
})();

View File

@ -0,0 +1,299 @@
// FIXME: Use the real promise if available.
// FIXME: Make sure this interface is compatible with the real Promise.
function SimplePromise() {
this._chainedPromise = null;
this._callback = null;
}
SimplePromise.prototype.then = function (callback) {
if (this._callback)
throw "SimplePromise doesn't support multiple calls to then";
this._callback = callback;
this._chainedPromise = new SimplePromise;
if (this._resolved)
this.resolve(this._resolvedValue);
return this._chainedPromise;
}
SimplePromise.prototype.resolve = function (value) {
if (!this._callback) {
this._resolved = true;
this._resolvedValue = value;
return;
}
var result = this._callback(value);
if (result instanceof SimplePromise) {
var chainedPromise = this._chainedPromise;
result.then(function (result) { chainedPromise.resolve(result); });
} else
this._chainedPromise.resolve(result);
}
function BenchmarkTestStep(testName, testFunction) {
this.name = testName;
this.run = testFunction;
}
function BenchmarkRunner(suites, client) {
this._suites = suites;
this._prepareReturnValue = null;
this._client = client;
}
BenchmarkRunner.prototype.waitForElement = function (selector) {
var promise = new SimplePromise;
var contentDocument = this._frame.contentDocument;
function resolveIfReady() {
var element = contentDocument.querySelector(selector);
if (element)
return promise.resolve(element);
setTimeout(resolveIfReady, 50);
}
resolveIfReady();
return promise;
}
BenchmarkRunner.prototype._removeFrame = function () {
if (this._frame) {
this._frame.parentNode.removeChild(this._frame);
this._frame = null;
}
}
BenchmarkRunner.prototype._appendFrame = function (src) {
var frame = document.createElement('iframe');
frame.style.width = '800px';
frame.style.height = '600px';
frame.style.border = '0px none';
frame.style.position = 'absolute';
frame.setAttribute('scrolling', 'no');
var marginLeft = parseInt(getComputedStyle(document.body).marginLeft);
var marginTop = parseInt(getComputedStyle(document.body).marginTop);
if (window.innerWidth > 800 + marginLeft && window.innerHeight > 600 + marginTop) {
frame.style.left = marginLeft + 'px';
frame.style.top = marginTop + 'px';
} else {
frame.style.left = '0px';
frame.style.top = '0px';
}
if (this._client && this._client.willAddTestFrame)
this._client.willAddTestFrame(frame);
document.body.insertBefore(frame, document.body.firstChild);
this._frame = frame;
return frame;
}
BenchmarkRunner.prototype._waitAndWarmUp = function () {
var startTime = Date.now();
function Fibonacci(n) {
if (Date.now() - startTime > 100)
return;
if (n <= 0)
return 0;
else if (n == 1)
return 1;
return Fibonacci(n - 2) + Fibonacci(n - 1);
}
var promise = new SimplePromise;
setTimeout(function () {
Fibonacci(100);
promise.resolve();
}, 200);
return promise;
}
BenchmarkRunner.prototype._writeMark = function(name) {
if (window.performance && window.performance.mark)
window.performance.mark(name);
}
// This function ought be as simple as possible. Don't even use SimplePromise.
BenchmarkRunner.prototype._runTest = function(suite, test, prepareReturnValue, callback)
{
var self = this;
var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
var contentWindow = self._frame.contentWindow;
var contentDocument = self._frame.contentDocument;
self._writeMark(suite.name + '.' + test.name + '-start');
var startTime = now();
test.run(prepareReturnValue, contentWindow, contentDocument);
var endTime = now();
self._writeMark(suite.name + '.' + test.name + '-sync-end');
var syncTime = endTime - startTime;
var startTime = now();
setTimeout(function () {
// Some browsers don't immediately update the layout for paint.
// Force the layout here to ensure we're measuring the layout time.
var height = self._frame.contentDocument.body.getBoundingClientRect().height;
var endTime = now();
self._frame.contentWindow._unusedHeightValue = height; // Prevent dead code elimination.
self._writeMark(suite.name + '.' + test.name + '-async-end');
callback(syncTime, endTime - startTime, height);
}, 0);
}
function BenchmarkState(suites) {
this._suites = suites;
this._suiteIndex = -1;
this._testIndex = 0;
this.next();
}
BenchmarkState.prototype.currentSuite = function() {
return this._suites[this._suiteIndex];
}
BenchmarkState.prototype.currentTest = function () {
var suite = this.currentSuite();
return suite ? suite.tests[this._testIndex] : null;
}
BenchmarkState.prototype.next = function () {
this._testIndex++;
var suite = this._suites[this._suiteIndex];
if (suite && this._testIndex < suite.tests.length)
return this;
this._testIndex = 0;
do {
this._suiteIndex++;
} while (this._suiteIndex < this._suites.length && this._suites[this._suiteIndex].disabled);
return this;
}
BenchmarkState.prototype.isFirstTest = function () {
return !this._testIndex;
}
BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
var suite = this.currentSuite();
var promise = new SimplePromise;
frame.onload = function () {
suite.prepare(runner, frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
}
frame.src = 'resources/' + suite.url;
return promise;
}
BenchmarkRunner.prototype.step = function (state) {
if (!state) {
state = new BenchmarkState(this._suites);
this._measuredValues = {tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN};
}
var suite = state.currentSuite();
if (!suite) {
this._finalize();
var promise = new SimplePromise;
promise.resolve();
return promise;
}
if (state.isFirstTest()) {
this._removeFrame();
var self = this;
return state.prepareCurrentSuite(this, this._appendFrame()).then(function (prepareReturnValue) {
self._prepareReturnValue = prepareReturnValue;
return self._runTestAndRecordResults(state);
});
}
return this._runTestAndRecordResults(state);
}
BenchmarkRunner.prototype.runAllSteps = function (startingState) {
var nextCallee = this.runAllSteps.bind(this);
this.step(startingState).then(function (nextState) {
if (nextState)
nextCallee(nextState);
});
}
BenchmarkRunner.prototype.runMultipleIterations = function (iterationCount) {
var self = this;
var currentIteration = 0;
this._runNextIteration = function () {
currentIteration++;
if (currentIteration < iterationCount)
self.runAllSteps();
else if (this._client && this._client.didFinishLastIteration)
this._client.didFinishLastIteration();
}
if (this._client && this._client.willStartFirstIteration)
this._client.willStartFirstIteration(iterationCount);
self.runAllSteps();
}
BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
var promise = new SimplePromise;
var suite = state.currentSuite();
var test = state.currentTest();
if (this._client && this._client.willRunTest)
this._client.willRunTest(suite, test);
var self = this;
setTimeout(function () {
self._runTest(suite, test, self._prepareReturnValue, function (syncTime, asyncTime) {
var suiteResults = self._measuredValues.tests[suite.name] || {tests:{}, total: 0};
var total = syncTime + asyncTime;
self._measuredValues.tests[suite.name] = suiteResults;
suiteResults.tests[test.name] = {tests: {'Sync': syncTime, 'Async': asyncTime}, total: total};
suiteResults.total += total;
if (self._client && self._client.didRunTest)
self._client.didRunTest(suite, test);
state.next();
promise.resolve(state);
});
}, 0);
return promise;
}
BenchmarkRunner.prototype._finalize = function () {
this._removeFrame();
if (this._client && this._client.didRunSuites) {
var product = 1;
var values = [];
for (var suiteName in this._measuredValues.tests) {
var suiteTotal = this._measuredValues.tests[suiteName].total;
product *= suiteTotal;
values.push(suiteTotal);
}
values.sort(function (a, b) { return a - b }); // Avoid the loss of significance for the sum.
var total = values.reduce(function (a, b) { return a + b });
var geomean = Math.pow(product, 1 / values.length);
var correctionFactor = 1.5; // This factor makes the test score look reasonably fit within 0 to 140.
this._measuredValues.total = total;
this._measuredValues.mean = total / values.length;
this._measuredValues.geomean = geomean;
this._measuredValues.score = 60 * 1000 / geomean / correctionFactor;
this._client.didRunSuites(this._measuredValues);
}
if (this._runNextIteration)
this._runNextIteration();
}

View File

@ -0,0 +1,286 @@
body {
background-color: rgb(46, 51, 55);
color: rgb(235, 235, 235);
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
::selection {
color: rgb(46, 51, 55);
background-color: rgb(235, 235, 235);
}
h1,
button {
font-family: "Futura-Medium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
code {
font-family: Menlo, Monaco, monospace;
font-size: smaller;
}
button {
cursor: pointer;
}
hr {
border: 1px solid rgb(235, 235, 235);
width: 50%;
margin: 40px auto;
}
img {
-webkit-user-select: none;
-webkit-user-drag: none;
}
main {
display: block;
position: absolute;
width: 800px;
height: 600px;
top: 50%;
left: 50%;
margin-top: -321px;
margin-left: -421px;
padding: 15px;
border: 6px solid rgb(235, 235, 235);
border-radius: 20px;
}
#logo {
position: absolute;
left: -70px;
top: 115px;
width: 75px;
height: 406px;
}
h1 {
margin-top: 30px;
font-size: 40px;
font-weight: normal;
color: rgb(235, 235, 235);
text-align: center;
}
p {
font-size: 16px;
line-height: 21px;
}
a {
color: inherit;
}
.buttons {
margin-top: 30px;
text-align: center;
}
button {
-webkit-appearance: none;
border: 3px solid rgb(235, 235, 235);
border-radius: 10px;
min-width: 200px;
padding: 5px 20px;
margin: 0 40px;
font-size: 25px;
color: rgb(235, 235, 235);
background-color: transparent;
-webkit-user-select: none;
}
button:active {
background-color: rgb(235, 235, 235);
color: rgb(46, 51, 55);
border-color: rgb(235, 235, 235) !important;
}
button:focus {
outline: none;
border-color: rgb(232, 79, 79);
}
section {
display: none;
}
section > p {
margin: 10px 20px;
}
section.selected {
display: block;
}
#testContainer {
position: absolute;
top: 15px;
left: 15px;
width: 800px;
height: 600px;
}
section#home > p {
margin: 0 auto;
width: 70%;
text-align: center;
}
section#home > p:first-child {
margin-top: 160px;
text-align: center;
}
section#home > .show-about {
margin-top: 100px;
}
section#home > .buttons {
margin-top: 80px;
}
section#running > #progress {
position: absolute;
bottom: -6px;
left: 60px;
right: 60px;
height: 6px;
background-color: rgb(128, 128, 128);
border-left: 6px solid rgb(46, 51, 55);
border-right: 6px solid rgb(46, 51, 55);
}
section#running #progress-completed {
position: absolute;
top: 0;
left: 0;
height: 6px;
width: 0;
background-color: rgb(235, 235, 235);
}
section#running > #info {
position: absolute;
bottom: -25px;
left: 60px;
right: 60px;
height: 12px;
color: rgb(128, 128, 128);
text-align: center;
font-size: 12px;
}
section#summarized-results > #result-number,
section#summarized-results > #confidence-number {
font-family: "Futura-CondensedMedium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
}
section#summarized-results > #result-number {
text-align: center;
font-size: 145px;
line-height: 145px;
}
section#summarized-results > #confidence-number {
text-align: center;
font-size: 36px;
line-height: 36px;
color: rgb(128, 128, 128);
}
section#detailed-results > table {
float: left;
width: 50%;
}
section#detailed-results > .arithmetic-mean {
clear: both;
padding-top: 32px;
text-align: center;
}
section#detailed-results > .arithmetic-mean > label {
font-weight: bold;
margin-right: 10px;
color: rgb(128, 128, 128);
}
section#detailed-results > .show-about {
margin-top: 30px;
text-align: center;
}
section#about h1 {
margin-top: 10px;
margin-bottom: 0px;
font-size: 25px;
}
section#about .note {
color: rgb(128, 128, 128);
}
table {
border-spacing: 0;
border-collapse: collapse;
}
th,
td {
padding: 5px;
}
th {
text-align: right;
color: rgb(128, 128, 128);
}
.gauge {
position: relative;
width: 738px;
height: 78px;
background-image: url(gauge.png);
background-size: 100% 100%;
background-repat: no-repeat;
margin: 0 auto;
}
.gauge > .window {
position: absolute;
left: 0;
top: 33px;
bottom: 0;
right: 0;
overflow: hidden;
}
.gauge > .window > .needle {
position: absolute;
left: 363px;
bottom: -88px;
width: 4px;
height: 400px;
background-color: rgb(247, 148, 29);
-webkit-transform: rotate(-70deg);
-webkit-transform-origin: 2px 400px;
-moz-transform: rotate(-70deg);
-moz-transform-origin: 2px 400px;
transform: rotate(-70deg);
transform-origin: 2px 400px;
}
@media (-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi) {
#logo {
content: url(logo@2x.png); /* FIXME: This does not work in Firefox. */
}
.gauge {
background-image: url(gauge@2x.png);
}
}

View File

@ -0,0 +1,246 @@
window.benchmarkClient = {
displayUnit: 'runs/min',
iterationCount: 10,
stepCount: null,
suitesCount: null,
_measuredValuesList: [],
_finishedTestCount: 0,
_progressCompleted: null,
willAddTestFrame: function (frame) {
var main = document.querySelector('main');
var style = getComputedStyle(main);
frame.style.left = main.offsetLeft + parseInt(style.borderLeftWidth) + parseInt(style.paddingLeft) + 'px';
frame.style.top = main.offsetTop + parseInt(style.borderTopWidth) + parseInt(style.paddingTop) + 'px';
},
willRunTest: function (suite, test) {
document.getElementById('info').textContent = suite.name + ' ( ' + this._finishedTestCount + ' / ' + this.stepCount + ' )';
},
didRunTest: function () {
this._finishedTestCount++;
this._progressCompleted.style.width = (this._finishedTestCount * 100 / this.stepCount) + '%';
},
didRunSuites: function (measuredValues) {
this._measuredValuesList.push(measuredValues);
},
willStartFirstIteration: function () {
this._measuredValuesList = [];
this._finishedTestCount = 0;
this._progressCompleted = document.getElementById('progress-completed');
document.getElementById('logo-link').onclick = function (event) { event.preventDefault(); return false; }
},
didFinishLastIteration: function () {
document.getElementById('logo-link').onclick = null;
var results = this._computeResults(this._measuredValuesList, this.displayUnit);
this._updateGaugeNeedle(results.mean);
document.getElementById('result-number').textContent = results.formattedMean;
if (results.formattedDelta)
document.getElementById('confidence-number').textContent = '\u00b1 ' + results.formattedDelta;
this._populateDetailedResults(results.formattedValues);
document.getElementById('results-with-statistics').textContent = results.formattedMeanAndDelta;
if (this.displayUnit == 'ms') {
document.getElementById('show-summary').style.display = 'none';
showResultDetails();
} else
showResultsSummary();
},
_computeResults: function (measuredValuesList, displayUnit) {
var suitesCount = this.suitesCount;
function valueForUnit(measuredValues) {
if (displayUnit == 'ms')
return measuredValues.geomean;
return measuredValues.score;
}
function sigFigFromPercentDelta(percentDelta) {
return Math.ceil(-Math.log(percentDelta)/Math.log(10)) + 3;
}
function toSigFigPrecision(number, sigFig) {
var nonDecimalDigitCount = number < 1 ? 0 : (Math.floor(Math.log(number)/Math.log(10)) + 1);
return number.toPrecision(Math.max(nonDecimalDigitCount, Math.min(6, sigFig)));
}
var values = measuredValuesList.map(valueForUnit);
var sum = values.reduce(function (a, b) { return a + b; }, 0);
var arithmeticMean = sum / values.length;
var meanSigFig = 4;
var formattedDelta;
var formattedPercentDelta;
if (window.Statistics) {
var delta = Statistics.confidenceIntervalDelta(0.95, values.length, sum, Statistics.squareSum(values));
if (!isNaN(delta)) {
var percentDelta = delta * 100 / arithmeticMean;
meanSigFig = sigFigFromPercentDelta(percentDelta);
formattedDelta = toSigFigPrecision(delta, 2);
formattedPercentDelta = toSigFigPrecision(percentDelta, 2) + '%';
}
}
var formattedMean = toSigFigPrecision(arithmeticMean, Math.max(meanSigFig, 3));
return {
formattedValues: values.map(function (value) {
return toSigFigPrecision(value, 4) + ' ' + displayUnit;
}),
mean: arithmeticMean,
formattedMean: formattedMean,
formattedDelta: formattedDelta,
formattedMeanAndDelta: formattedMean + (formattedDelta ? ' \xb1 ' + formattedDelta + ' (' + formattedPercentDelta + ')' : ''),
};
},
_addDetailedResultsRow: function (table, iterationNumber, value) {
var row = document.createElement('tr');
var th = document.createElement('th');
th.textContent = 'Iteration ' + (iterationNumber + 1);
var td = document.createElement('td');
td.textContent = value;
row.appendChild(th);
row.appendChild(td);
table.appendChild(row);
},
_updateGaugeNeedle: function (rpm) {
var needleAngle = Math.max(0, Math.min(rpm, 140)) - 70;
var needleRotationValue = 'rotate(' + needleAngle + 'deg)';
var gaugeNeedleElement = document.querySelector('#summarized-results > .gauge .needle');
gaugeNeedleElement.style.setProperty('-webkit-transform', needleRotationValue);
gaugeNeedleElement.style.setProperty('-moz-transform', needleRotationValue);
gaugeNeedleElement.style.setProperty('-ms-transform', needleRotationValue);
gaugeNeedleElement.style.setProperty('transform', needleRotationValue);
},
_populateDetailedResults: function (formattedValues) {
var resultsTables = document.querySelectorAll('.results-table');
var i = 0;
resultsTables[0].innerHTML = '';
for (; i < Math.ceil(formattedValues.length / 2); i++)
this._addDetailedResultsRow(resultsTables[0], i, formattedValues[i]);
resultsTables[1].innerHTML = '';
for (; i < formattedValues.length; i++)
this._addDetailedResultsRow(resultsTables[1], i, formattedValues[i]);
},
prepareUI: function () {
window.addEventListener('popstate', function (event) {
if (event.state) {
var sectionToShow = event.state.section;
if (sectionToShow) {
var sections = document.querySelectorAll('main > section');
for (var i = 0; i < sections.length; i++) {
if (sections[i].id === sectionToShow)
return showSection(sectionToShow, false);
}
}
}
return showSection('home', false);
}, false);
function updateScreenSize() {
// FIXME: Detect when the window size changes during the test.
var screenIsTooSmall = window.innerWidth < 850 || window.innerHeight < 650;
document.getElementById('screen-size').textContent = window.innerWidth + 'px by ' + window.innerHeight + 'px';
document.getElementById('screen-size-warning').style.display = screenIsTooSmall ? null : 'none';
}
window.addEventListener('resize', updateScreenSize);
updateScreenSize();
}
}
function enableOneSuite(suites, suiteToEnable)
{
suiteToEnable = suiteToEnable.toLowerCase();
var found = false;
for (var i = 0; i < suites.length; i++) {
var currentSuite = suites[i];
if (currentSuite.name.toLowerCase() == suiteToEnable) {
currentSuite.disabled = false;
found = true;
} else
currentSuite.disabled = true;
}
return found;
}
function startBenchmark() {
if (location.search.length > 1) {
var parts = location.search.substring(1).split('&');
for (var i = 0; i < parts.length; i++) {
var keyValue = parts[i].split('=');
var key = keyValue[0];
var value = keyValue[1];
switch (key) {
case 'unit':
if (value == 'ms')
benchmarkClient.displayUnit = 'ms';
else
console.error('Invalid unit: ' + value);
break;
case 'iterationCount':
var parsedValue = parseInt(value);
if (!isNaN(parsedValue))
benchmarkClient.iterationCount = parsedValue;
else
console.error('Invalid iteration count: ' + value);
break;
case 'suite':
if (!enableOneSuite(Suites, value)) {
alert('Suite "' + value + '" does not exist. No tests to run.');
return false;
}
break;
}
}
}
var enabledSuites = Suites.filter(function (suite) { return !suite.disabled; });
var totalSubtestsCount = enabledSuites.reduce(function (testsCount, suite) { return testsCount + suite.tests.length; }, 0);
benchmarkClient.stepCount = benchmarkClient.iterationCount * totalSubtestsCount;
benchmarkClient.suitesCount = enabledSuites.length;
var runner = new BenchmarkRunner(Suites, benchmarkClient);
runner.runMultipleIterations(benchmarkClient.iterationCount);
return true;
}
function showSection(sectionIdentifier, pushState) {
var currentSectionElement = document.querySelector('section.selected');
console.assert(currentSectionElement);
var newSectionElement = document.getElementById(sectionIdentifier);
console.assert(newSectionElement);
currentSectionElement.classList.remove('selected');
newSectionElement.classList.add('selected');
if (pushState)
history.pushState({section: sectionIdentifier}, document.title);
}
function showHome() {
showSection('home', true);
}
function startTest() {
if (startBenchmark())
showSection('running');
}
function showResultsSummary() {
showSection('summarized-results', true);
}
function showResultDetails() {
showSection('detailed-results', true);
}
function showAbout() {
showSection('about', true);
}
window.addEventListener('DOMContentLoaded', function () {
if (benchmarkClient.prepareUI)
benchmarkClient.prepareUI();
});

View File

@ -12,15 +12,27 @@ class Random
return this.seed = this.seed * 16807 % 2147483647;
}
underOne()
{
return (this.next % 1048576) / 1048576;
}
chance(chance)
{
return this.next % 1048576 < chance * 1048576;
return this.underOne() < chance;
}
number(under)
{
return this.next % under;
}
numberSquareWeightedToLow(under)
{
const random = this.underOne();
const random2 = random * random;
return Math.floor(random2 * under);
}
}
function nextAnimationFrame()
@ -35,9 +47,19 @@ class StyleBench
return {
name: 'Default',
elementTypeCount: 10,
idChance: 0.05,
elementChance: 0.5,
classCount: 200,
classChance: 0.3,
starChance: 0.05,
attributeChance: 0.02,
attributeCount: 10,
attributeValueCount: 20,
attributeOperators: ['','='],
elementClassChance: 0.5,
elementMaximumClasses: 3,
elementAttributeChance: 0.2,
elementMaximumAttributes: 3,
combinators: [' ', '>',],
pseudoClasses: [],
pseudoClassChance: 0,
@ -49,9 +71,11 @@ class StyleBench
maximumTreeWidth: 50,
repeatingSequenceChance: 0.2,
repeatingSequenceMaximumLength: 3,
leafClassMutationChance: 0.1,
leafMutationChance: 0.1,
styleSeed: 1,
domSeed: 2,
stepCount: 5,
mutationsPerStep: 100,
};
}
@ -70,21 +94,32 @@ class StyleBench
});
}
static pseudoClassConfiguration()
static structuralPseudoClassConfiguration()
{
return Object.assign(this.defaultConfiguration(), {
name: 'Positional pseudo classes',
name: 'Structural pseudo classes',
pseudoClassChance: 0.1,
pseudoClasses: [
'first-child',
'last-child',
'first-of-type',
'last-of-type',
'only-of-type',
'empty',
],
});
}
static nthPseudoClassConfiguration()
{
return Object.assign(this.defaultConfiguration(), {
name: 'Nth pseudo classes',
pseudoClassChance: 0.1,
pseudoClasses: [
'nth-child(2n+1)',
'nth-last-child(3n)',
'nth-of-type(3n)',
'nth-last-of-type(4n)',
'first-child',
'last-child',
'first-of-type',
'last-of-type',
'only-of-type',
],
});
}
@ -102,7 +137,8 @@ class StyleBench
return [
this.descendantCombinatorConfiguration(),
this.siblingCombinatorConfiguration(),
this.pseudoClassConfiguration(),
this.structuralPseudoClassConfiguration(),
this.nthPseudoClassConfiguration(),
this.beforeAndAfterConfiguration(),
];
}
@ -110,6 +146,7 @@ class StyleBench
constructor(configuration)
{
this.configuration = configuration;
this.idCount = 0;
this.baseStyle = document.createElement("style");
this.baseStyle.textContent = `
@ -119,10 +156,8 @@ class StyleBench
}
#testroot * {
display: inline-block;
}
#testroot :empty {
width:10px;
height:10px;
min-width:10px;
}
`;
document.head.appendChild(this.baseStyle);
@ -137,19 +172,36 @@ class StyleBench
randomElementName()
{
const elementTypeCount = this.configuration.elementTypeCount;
return `elem${ this.random.number(elementTypeCount) }`;
return `elem${ this.random.numberSquareWeightedToLow(elementTypeCount) }`;
}
randomClassName()
{
const classCount = this.configuration.classCount;
return `class${ this.random.number(classCount) }`;
return `class${ this.random.numberSquareWeightedToLow(classCount) }`;
}
randomClassNameFromRange(range)
{
const maximum = Math.round(range * this.configuration.classCount);
return `class${ this.random.number(maximum) }`;
return `class${ this.random.numberSquareWeightedToLow(maximum) }`;
}
randomAttributeName()
{
const attributeCount = this.configuration.attributeCount;
return `attr${ this.random.numberSquareWeightedToLow(attributeCount) }`;
}
randomAttributeValue()
{
const attributeValueCount = this.configuration.attributeValueCount;
const valueNum = this.random.numberSquareWeightedToLow(attributeValueCount);
if (valueNum == 0)
return "";
if (valueNum == 1)
return "val";
return `val${valueNum}`;
}
randomCombinator()
@ -158,28 +210,63 @@ class StyleBench
return combinators[this.random.number(combinators.length)]
}
randomPseudoClass()
randomPseudoClass(isLast)
{
const pseudoClasses = this.configuration.pseudoClasses;
return pseudoClasses[this.random.number(pseudoClasses.length)]
const pseudoClass = pseudoClasses[this.random.number(pseudoClasses.length)]
if (!isLast && pseudoClass == 'empty')
return this.randomPseudoClass(isLast);
return pseudoClass;
}
makeSimpleSelector(index, length)
randomId()
{
const idCount = this.configuration.idChance * this.configuration.elementCount ;
return `id${ this.random.number(idCount) }`;
}
randomAttributeSelector()
{
const name = this.randomAttributeName();
const operators = this.configuration.attributeOperators;
const operator = operators[this.random.numberSquareWeightedToLow(operators.length)];
if (operator == '')
return `[${name}]`;
const value = this.randomAttributeValue();
return `[${name}${operator}"${value}"]`;
}
makeCompoundSelector(index, length)
{
const isFirst = index == 0;
const isLast = index == length - 1;
const usePseudoClass = this.random.chance(this.configuration.pseudoClassChance) && this.configuration.pseudoClasses.length;
const useElement = usePseudoClass || this.random.chance(this.configuration.elementChance); // :nth-of-type etc only make sense with element
const useClass = !useElement || this.random.chance(this.configuration.classChance);
const useId = isFirst && this.random.chance(this.configuration.idChance);
const useElement = !useId && (usePseudoClass || this.random.chance(this.configuration.elementChance)); // :nth-of-type etc only make sense with element
const useAttribute = !useId && this.random.chance(this.configuration.attributeChance);
const useIdElementOrAttribute = useId || useElement || useAttribute;
const useStar = !useIdElementOrAttribute && !isFirst && this.random.chance(this.configuration.starChance);
const useClass = !useId && !useStar && (!useIdElementOrAttribute || this.random.chance(this.configuration.classChance));
const useBeforeOrAfter = isLast && this.random.chance(this.configuration.beforeAfterChance);
let result = "";
if (useElement)
result += this.randomElementName();
if (useStar)
result = "*";
if (useId)
result += "#" + this.randomId();
if (useClass) {
// Use a smaller pool of class names on the left side of the selectors to create containers.
result += "." + this.randomClassNameFromRange((index + 1) / length);
const classCount = this.random.numberSquareWeightedToLow(2) + 1;
for (let i = 0; i < classCount; ++i) {
// Use a smaller pool of class names on the left side of the selectors to create containers.
result += "." + this.randomClassNameFromRange((index + 1) / length);
}
}
if (useAttribute)
result += this.randomAttributeSelector();
if (usePseudoClass)
result += ":" + this.randomPseudoClass();
result += ":" + this.randomPseudoClass(isLast);
if (useBeforeOrAfter) {
if (this.random.chance(0.5))
result += "::before";
@ -192,12 +279,12 @@ class StyleBench
makeSelector()
{
const length = this.random.number(this.configuration.maximumSelectorLength) + 1;
let result = this.makeSimpleSelector(0, length);
for (let i = 0; i < length; ++i) {
let result = this.makeCompoundSelector(0, length);
for (let i = 1; i < length; ++i) {
const combinator = this.randomCombinator();
if (combinator != ' ')
result += " " + combinator;
result += " " + this.makeSimpleSelector(i, length);
result += " " + this.makeCompoundSelector(i, length);
}
return result;
}
@ -212,7 +299,7 @@ class StyleBench
let declaration = `background-color: rgb(${this.randomColorComponent}, ${this.randomColorComponent}, ${this.randomColorComponent});`;
if (selector.endsWith('::before') || selector.endsWith('::after'))
declaration += " content: '\xa0';";
declaration += " content: ''; min-width:5px; display:inline-block;";
return declaration;
}
@ -242,12 +329,23 @@ class StyleBench
makeElement()
{
const element = document.createElement(this.randomElementName());
const hasClasses = this.random.chance(0.5);
const hasClasses = this.random.chance(this.configuration.elementClassChance);
const hasAttributes = this.random.chance(this.configuration.elementAttributeChance);
if (hasClasses) {
const count = this.random.number(3) + 1;
const count = this.random.numberSquareWeightedToLow(this.configuration.elementMaximumClasses) + 1;
for (let i = 0; i < count; ++i)
element.classList.add(this.randomClassName());
}
if (hasAttributes) {
const count = this.random.number(this.configuration.elementMaximumAttributes) + 1;
for (let i = 0; i < count; ++i)
element.setAttribute(this.randomAttributeName(), this.randomAttributeValue());
}
const hasId = this.random.chance(this.configuration.idChance);
if (hasId) {
element.id = `id${ this.idCount }`;
this.idCount++;
}
return element;
}
@ -320,7 +418,7 @@ class StyleBench
for (let i = 0; i < count;) {
const element = this.randomTreeElement();
// There are more leaves than branches. Avoid skewing towards leaf mutations.
if (!element.firstChild && !this.random.chance(this.configuration.leafClassMutationChance))
if (!element.firstChild && !this.random.chance(this.configuration.leafMutationChance))
continue;
++i;
const classList = element.classList;
@ -333,7 +431,7 @@ class StyleBench
for (let i = 0; i < count;) {
const element = this.randomTreeElement();
const classList = element.classList;
if (!element.firstChild && !this.random.chance(this.configuration.leafClassMutationChance))
if (!element.firstChild && !this.random.chance(this.configuration.leafMutationChance))
continue;
if (!classList.length)
continue;
@ -371,6 +469,33 @@ class StyleBench
this.updateCachedTestElements();
}
mutateAttributes(count)
{
for (let i = 0; i < count;) {
const element = this.randomTreeElement();
// There are more leaves than branches. Avoid skewing towards leaf mutations.
if (!element.firstChild && !this.random.chance(this.configuration.leafMutationChance))
continue;
const attributeNames = element.getAttributeNames();
let mutatedAttributes = false;
for (const name of attributeNames) {
if (name == "class" || name == "id")
continue;
if (this.random.chance(0.5))
element.removeAttribute(name);
else
element.setAttribute(name, this.randomAttributeValue());
mutatedAttributes = true;
}
if (!mutatedAttributes) {
const attributeCount = this.random.number(this.configuration.elementMaximumAttributes) + 1;
for (let j = 0; j < attributeCount; ++j)
element.setAttribute(this.randomAttributeName(), this.randomAttributeValue());
}
++i;
}
}
async runForever()
{
while (true) {
@ -378,6 +503,7 @@ class StyleBench
this.removeClasses(10);
this.addLeafElements(10);
this.removeLeafElements(10);
this.mutateAttributes(10);
await nextAnimationFrame();
}

View File

@ -1,18 +1,21 @@
function makeSteps(count)
function makeSteps(configuration)
{
let steps = [];
for (let i = 0; i < count; ++i) {
steps.push(new BenchmarkTestStep('Adding classes', (bench, contentWindow, contentDocument) => {
bench.addClasses(100);
const steps = [];
for (i = 0; i < configuration.stepCount; ++i) {
steps.push(new BenchmarkTestStep(`Adding classes - ${i}`, (bench, contentWindow, contentDocument) => {
bench.addClasses(configuration.mutationsPerStep);
}));
steps.push(new BenchmarkTestStep('Removing classes', (bench, contentWindow, contentDocument) => {
bench.removeClasses(100);
steps.push(new BenchmarkTestStep(`Removing classes - ${i}`, (bench, contentWindow, contentDocument) => {
bench.removeClasses(configuration.mutationsPerStep);
}));
steps.push(new BenchmarkTestStep('Adding leaf elements', (bench, contentWindow, contentDocument) => {
bench.addLeafElements(100);
steps.push(new BenchmarkTestStep(`Mutating attributes - ${i}`, (bench, contentWindow, contentDocument) => {
bench.mutateAttributes(configuration.mutationsPerStep);
}));
steps.push(new BenchmarkTestStep('Removing leaf elements', (bench, contentWindow, contentDocument) => {
bench.removeLeafElements(100);
steps.push(new BenchmarkTestStep(`Adding leaf elements - ${i}`, (bench, contentWindow, contentDocument) => {
bench.addLeafElements(configuration.mutationsPerStep);
}));
steps.push(new BenchmarkTestStep(`Removing leaf elements - ${i}`, (bench, contentWindow, contentDocument) => {
bench.removeLeafElements(configuration.mutationsPerStep);
}));
}
return steps;
@ -28,7 +31,7 @@ function makeSuite(configuration)
return contentWindow.createBenchmark(configuration);
});
},
tests: makeSteps(5),
tests: makeSteps(configuration),
};
}