diff --git a/MotionMark/developer.html b/MotionMark/developer.html
index b2de742..d6974fa 100644
--- a/MotionMark/developer.html
+++ b/MotionMark/developer.html
@@ -99,6 +99,15 @@
Adjusting the test complexity:
+
+ Score profile:
+
+
diff --git a/MotionMark/resources/debug-runner/debug-runner.js b/MotionMark/resources/debug-runner/debug-runner.js
index 085f4d3..3cf9491 100644
--- a/MotionMark/resources/debug-runner/debug-runner.js
+++ b/MotionMark/resources/debug-runner/debug-runner.js
@@ -214,7 +214,7 @@ window.optionsManager = new class OptionsManager {
options[name] = formElement.checked;
else if (type == "radio") {
var radios = formElements[name];
- if (radios.constructor === HTMLCollection) {
+ if (radios.constructor === RadioNodeList || radios.constructor === HTMLCollection) {
for (var j = 0; j < radios.length; ++j) {
var radio = radios[j];
if (radio.checked) {
@@ -657,7 +657,8 @@ class DebugBenchmarkController extends BenchmarkController {
startBenchmark()
{
benchmarkController.determineCanvasSize();
- benchmarkController.options = Utilities.mergeObjects(this.benchmarkDefaultParameters, optionsManager.updateLocalStorageFromUI());
+ const optionsFromUI = optionsManager.updateLocalStorageFromUI();
+ benchmarkController.options = Utilities.mergeObjects(this.benchmarkDefaultParameters, optionsFromUI);
benchmarkController.suites = suitesManager.updateLocalStorageFromUI();
this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test");
}
diff --git a/MotionMark/resources/runner/results.js b/MotionMark/resources/runner/results.js
index 291992a..6eea718 100644
--- a/MotionMark/resources/runner/results.js
+++ b/MotionMark/resources/runner/results.js
@@ -180,6 +180,7 @@ class ScoreCalculator {
const frameTypeIndex = series.fieldMap[Strings.json.frameType];
const complexityIndex = series.fieldMap[complexityKey];
+ const frameTimeIndex = series.fieldMap[Strings.json.time];
const frameLengthIndex = series.fieldMap[Strings.json.frameLength];
const regressionOptions = { desiredFrameLength: desiredFrameLength };
if (profile)
@@ -187,7 +188,7 @@ class ScoreCalculator {
const regressionSamples = series.slice(minIndex, maxIndex + 1);
const animationSamples = regressionSamples.data.filter((sample) => sample[frameTypeIndex] == Strings.json.animationFrameType);
- const regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex] ]);
+ const regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex], sample[frameTimeIndex] ]);
const regression = new Regression(regressionData, minIndex, maxIndex, regressionOptions);
return {
@@ -266,6 +267,7 @@ class ScoreCalculator {
const resample = new SampleData(regressionResult.samples.fieldMap, resampleData);
const bootstrapRegressionResult = findRegression(resample, predominantProfile);
+ //console.log('regression', bootstrapRegressionResult.regression);
if (bootstrapRegressionResult.regression.t2 < 0) {
// A positive slope means the frame rate decreased with increased complexity (which is the expected
// benavior). OTOH, a negative slope means the framerate increased as the complexity increased. This
diff --git a/MotionMark/resources/statistics.js b/MotionMark/resources/statistics.js
index bd0d7e3..d8e76ee 100644
--- a/MotionMark/resources/statistics.js
+++ b/MotionMark/resources/statistics.js
@@ -178,8 +178,10 @@ class Regression {
constructor(samples, startIndex, endIndex, options)
{
const desiredFrameLength = options.desiredFrameLength;
- var profile;
+ const kWindowSizeMultiple = 0.1;
+ var profile;
+
if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.slope) {
profile = this._calculateRegression(samples, {
shouldClip: true,
@@ -195,6 +197,11 @@ class Regression {
t2: 0
});
this.profile = Strings.json.profiles.flat;
+ } else if (options.preferredProfile == Strings.json.profiles.window || options.preferredProfile == Strings.json.profiles.windowStrict) {
+ const window_size = Math.max(1, Math.floor(samples.length * kWindowSizeMultiple));
+ const strict = options.preferredProfile == Strings.json.profiles.windowStrict;
+ profile = this._windowedFit(samples, desiredFrameLength, window_size, strict);
+ this.profile = options.preferredProfile;
}
this.startIndex = Math.min(startIndex, endIndex);
@@ -219,6 +226,111 @@ class Regression {
return this.s1 + this.t1 * complexity;
}
+ _windowedFit(samples, desiredFrameLength, windowSize, strict)
+ {
+ const kAllowedErrorFactor = 0.9;
+
+ const complexityIndex = 0;
+ const frameLengthIndex = 1;
+ const frameTimeIndex = 2;
+
+ const average = array => array.reduce((a, b) => a + b) / array.length;
+
+ var sortedSamples = samples.slice().sort((a, b) => {
+ if (a[complexityIndex] == b[complexityIndex])
+ return b[frameTimeIndex] - a[frameTimeIndex];
+ return a[complexityIndex] - b[complexityIndex];
+ });
+
+ var cumFrameLength = 0.0;
+ var bestIndex = 0;
+ var bestComplexity = 0;
+ var runningFrameLengths = [];
+ var runningComplexities = [];
+
+ for (var i = 0; i < sortedSamples.length; ++i) {
+ runningFrameLengths.push(sortedSamples[i][frameLengthIndex]);
+ runningComplexities.push(sortedSamples[i][complexityIndex]);
+
+ if (runningFrameLengths.length < windowSize) {
+ continue
+ } else if (runningFrameLengths.length > windowSize) {
+ runningFrameLengths.shift();
+ runningComplexities.shift();
+ }
+
+ let averageFrameLength = average(runningFrameLengths);
+ let averageComplexity = average(runningComplexities);
+ let error = desiredFrameLength / averageFrameLength;
+ let adjustedComplexity = averageComplexity * Math.min(1.0, error);
+
+ if (error >= kAllowedErrorFactor) {
+ if (adjustedComplexity > bestComplexity) {
+ bestComplexity = adjustedComplexity;
+ }
+ } else if (strict) {
+ break;
+ }
+ }
+
+ for (var i = 0; i < sortedSamples.length; ++i) {
+ if (sortedSamples[i][complexityIndex] <= bestComplexity)
+ bestIndex = i;
+ }
+
+ let complexity = bestComplexity;
+
+ // Calculate slope for remaining points
+ let t_nom = 0.0;
+ let t_denom = 0.0;
+ for (var i = bestIndex + 1; i < sortedSamples.length; i++) {
+ const tx = sortedSamples[i][complexityIndex] - complexity;
+ const ty = sortedSamples[i][frameLengthIndex] - desiredFrameLength;
+ t_nom += tx * ty;
+ t_denom += tx * tx;
+ }
+
+ var s1 = desiredFrameLength;
+ var t1 = 0;
+ var t2 = (t_nom / t_denom) || 0.0;
+ var s2 = desiredFrameLength - t2 * complexity;
+ var n1 = bestIndex + 1;
+ var n2 = sortedSamples.length - bestIndex - 1;
+
+ function getValueAt(at_complexity)
+ {
+ if (at_complexity > complexity)
+ return s2 + t2 * complexity;
+ return s1 + t1 * complexity;
+ }
+
+ let error1 = 0.0;
+ let error2 = 0.0;
+ for (var i = 0; i < n1; ++i) {
+ const frameLengthErr = sortedSamples[i][frameLengthIndex] - getValueAt(sortedSamples[i][complexityIndex]);
+ error1 += frameLengthErr * frameLengthErr;
+ }
+ for (var i = n1; i < sortedSamples.length; ++i) {
+ const frameLengthErr = sortedSamples[i][frameLengthIndex] - getValueAt(sortedSamples[i][complexityIndex]);
+ error2 += frameLengthErr * frameLengthErr;
+ }
+
+ return {
+ s1: s1,
+ t1: t1,
+ s2: s2,
+ t2: t2,
+ complexity: complexity,
+ // Number of samples included in the first segment, inclusive of bestIndex
+ n1: n1,
+ // Number of samples included in the second segment
+ n2: n2,
+ stdev1: Math.sqrt(error1 / n1),
+ stdev2: Math.sqrt(error2 / n2),
+ error: error1 + error2,
+ };
+ }
+
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
//
// Minimize sum of (y - y')^2
diff --git a/MotionMark/resources/strings.js b/MotionMark/resources/strings.js
index 43e0510..735e901 100644
--- a/MotionMark/resources/strings.js
+++ b/MotionMark/resources/strings.js
@@ -78,7 +78,9 @@ var Strings = {
profiles: {
slope: "slope",
- flat: "flat"
+ flat: "flat",
+ window: "window",
+ windowStrict: "window-strict",
},
results: {
diff --git a/MotionMark/tests/resources/controllers.js b/MotionMark/tests/resources/controllers.js
index 25c2720..3788d52 100644
--- a/MotionMark/tests/resources/controllers.js
+++ b/MotionMark/tests/resources/controllers.js
@@ -170,10 +170,15 @@ class Controller {
{
return samples[sampleTimeIndex][i] - samples[sampleTimeIndex][i - 1];
}
+
+ _getFrameTime(samples, i)
+ {
+ return samples[sampleTimeIndex][i];
+ }
_previousFrameComplexity(samples, i)
{
- if (i > 0)
+ if (i > 1)
return this._getComplexity(samples, i - 1);
return 0;
@@ -399,6 +404,7 @@ class RampController extends Controller {
super(benchmark, options);
this.targetFPS = targetFPS;
+ this.preferredProfile = options["score-profile"];
// Initially start with a tier test to find the bounds
// The number of objects in a tier test is 10^|_tier|
@@ -586,10 +592,15 @@ class RampController extends Controller {
for (var i = this._rampStartIndex; i < this._sampler.sampleCount; ++i) {
if (this._getFrameType(this._sampler.samples, i) == Strings.json.mutationFrameType)
continue;
- regressionData.push([ this._getComplexity(this._sampler.samples, i), this._getFrameLength(this._sampler.samples, i) ]);
+ regressionData.push(
+ [
+ this._getComplexity(this._sampler.samples, i),
+ this._getFrameLength(this._sampler.samples, i),
+ this._getFrameTime(this._sampler.samples, i)
+ ]);
}
- var regression = new Regression(regressionData, this._sampler.sampleCount - 1, this._rampStartIndex, { desiredFrameLength: this.frameLengthDesired });
+ var regression = new Regression(regressionData, this._sampler.sampleCount - 1, this._rampStartIndex, { desiredFrameLength: this.frameLengthDesired, preferredProfile: this.preferredProfile });
this._rampRegressions.push(regression);
var frameLengthAtMaxComplexity = regression.valueAt(this._maximumComplexity);