Skip to content

Commit 6629414

Browse files
committed
Add new experimental 'window' regression function.
1 parent 032f503 commit 6629414

File tree

6 files changed

+150
-9
lines changed

6 files changed

+150
-9
lines changed

MotionMark/developer.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ <h3>Adjusting the test complexity:</h3>
9999
<li><label><input name="controller" type="radio" value="adaptive"> Maintain target FPS</label></li>
100100
</ul>
101101
</li>
102+
<li>
103+
<h3>Score profile:</h3>
104+
<ul>
105+
<li><label><input name="score-profile" type="radio" value="slope" checked> Slope</label></li>
106+
<li><label><input name="score-profile" type="radio" value="flat"> Flat</label></li>
107+
<li><label><input name="score-profile" type="radio" value="window"> Windowed</label></li>
108+
<li><label><input name="score-profile" type="radio" value="window-strict"> Windowed Strict</label></li>
109+
</ul>
110+
</li>
102111
<li>
103112
<label>System frame rate: <input type="number" id="system-frame-rate" value="60"> FPS</label><br>
104113
<label>Target frame rate: <input type="number" id="frame-rate" value="60"> FPS</label>

MotionMark/resources/debug-runner/debug-runner.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ window.optionsManager = new class OptionsManager {
214214
options[name] = formElement.checked;
215215
else if (type == "radio") {
216216
var radios = formElements[name];
217-
if (radios.constructor === HTMLCollection) {
217+
if (radios.constructor === RadioNodeList || radios.constructor === HTMLCollection) {
218218
for (var j = 0; j < radios.length; ++j) {
219219
var radio = radios[j];
220220
if (radio.checked) {
@@ -657,7 +657,8 @@ class DebugBenchmarkController extends BenchmarkController {
657657
startBenchmark()
658658
{
659659
benchmarkController.determineCanvasSize();
660-
benchmarkController.options = Utilities.mergeObjects(this.benchmarkDefaultParameters, optionsManager.updateLocalStorageFromUI());
660+
const optionsFromUI = optionsManager.updateLocalStorageFromUI();
661+
benchmarkController.options = Utilities.mergeObjects(this.benchmarkDefaultParameters, optionsFromUI);
661662
benchmarkController.suites = suitesManager.updateLocalStorageFromUI();
662663
this._startBenchmark(benchmarkController.suites, benchmarkController.options, "running-test");
663664
}

MotionMark/resources/runner/results.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,15 @@ class ScoreCalculator {
180180

181181
const frameTypeIndex = series.fieldMap[Strings.json.frameType];
182182
const complexityIndex = series.fieldMap[complexityKey];
183+
const frameTimeIndex = series.fieldMap[Strings.json.time];
183184
const frameLengthIndex = series.fieldMap[Strings.json.frameLength];
184185
const regressionOptions = { desiredFrameLength: desiredFrameLength };
185186
if (profile)
186187
regressionOptions.preferredProfile = profile;
187188

188189
const regressionSamples = series.slice(minIndex, maxIndex + 1);
189190
const animationSamples = regressionSamples.data.filter((sample) => sample[frameTypeIndex] == Strings.json.animationFrameType);
190-
const regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex] ]);
191+
const regressionData = animationSamples.map((sample) => [ sample[complexityIndex], sample[frameLengthIndex], sample[frameTimeIndex] ]);
191192

192193
const regression = new Regression(regressionData, minIndex, maxIndex, regressionOptions);
193194
return {
@@ -263,6 +264,7 @@ class ScoreCalculator {
263264

264265
const resample = new SampleData(regressionResult.samples.fieldMap, resampleData);
265266
const bootstrapRegressionResult = findRegression(resample, predominantProfile);
267+
//console.log('regression', bootstrapRegressionResult.regression);
266268
if (bootstrapRegressionResult.regression.t2 < 0) {
267269
// A positive slope means the frame rate decreased with increased complexity (which is the expected
268270
// benavior). OTOH, a negative slope means the framerate increased as the complexity increased. This

MotionMark/resources/statistics.js

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,10 @@ class Regression {
178178
constructor(samples, startIndex, endIndex, options)
179179
{
180180
const desiredFrameLength = options.desiredFrameLength;
181-
var profile;
181+
const kWindowSizeMultiple = 0.1;
182182

183+
var profile;
184+
183185
if (!options.preferredProfile || options.preferredProfile == Strings.json.profiles.slope) {
184186
profile = this._calculateRegression(samples, {
185187
shouldClip: true,
@@ -195,6 +197,11 @@ class Regression {
195197
t2: 0
196198
});
197199
this.profile = Strings.json.profiles.flat;
200+
} else if (options.preferredProfile == Strings.json.profiles.window || options.preferredProfile == Strings.json.profiles.windowStrict) {
201+
const window_size = Math.max(1, Math.floor(samples.length * kWindowSizeMultiple));
202+
const strict = options.preferredProfile == Strings.json.profiles.windowStrict;
203+
profile = this._windowedFit(samples, desiredFrameLength, window_size, strict);
204+
this.profile = options.preferredProfile;
198205
}
199206

200207
this.startIndex = Math.min(startIndex, endIndex);
@@ -219,6 +226,111 @@ class Regression {
219226
return this.s1 + this.t1 * complexity;
220227
}
221228

229+
_windowedFit(samples, desiredFrameLength, windowSize, strict)
230+
{
231+
const kAllowedErrorFactor = 0.9;
232+
233+
const complexityIndex = 0;
234+
const frameLengthIndex = 1;
235+
const frameTimeIndex = 2;
236+
237+
const average = array => array.reduce((a, b) => a + b) / array.length;
238+
239+
var sortedSamples = samples.slice().sort((a, b) => {
240+
if (a[complexityIndex] == b[complexityIndex])
241+
return b[frameTimeIndex] - a[frameTimeIndex];
242+
return a[complexityIndex] - b[complexityIndex];
243+
});
244+
245+
var cumFrameLength = 0.0;
246+
var bestIndex = 0;
247+
var bestComplexity = 0;
248+
var runningFrameLengths = [];
249+
var runningComplexities = [];
250+
251+
for (var i = 0; i < sortedSamples.length; ++i) {
252+
runningFrameLengths.push(sortedSamples[i][frameLengthIndex]);
253+
runningComplexities.push(sortedSamples[i][complexityIndex]);
254+
255+
if (runningFrameLengths.length < windowSize) {
256+
continue
257+
} else if (runningFrameLengths.length > windowSize) {
258+
runningFrameLengths.shift();
259+
runningComplexities.shift();
260+
}
261+
262+
let averageFrameLength = average(runningFrameLengths);
263+
let averageComplexity = average(runningComplexities);
264+
let error = desiredFrameLength / averageFrameLength;
265+
let adjustedComplexity = averageComplexity * Math.min(1.0, error);
266+
267+
if (error >= kAllowedErrorFactor) {
268+
if (adjustedComplexity > bestComplexity) {
269+
bestComplexity = adjustedComplexity;
270+
}
271+
} else if (strict) {
272+
break;
273+
}
274+
}
275+
276+
for (var i = 0; i < sortedSamples.length; ++i) {
277+
if (sortedSamples[i][complexityIndex] <= bestComplexity)
278+
bestIndex = i;
279+
}
280+
281+
let complexity = bestComplexity;
282+
283+
// Calculate slope for remaining points
284+
let t_nom = 0.0;
285+
let t_denom = 0.0;
286+
for (var i = bestIndex + 1; i < sortedSamples.length; i++) {
287+
const tx = sortedSamples[i][complexityIndex] - complexity;
288+
const ty = sortedSamples[i][frameLengthIndex] - desiredFrameLength;
289+
t_nom += tx * ty;
290+
t_denom += tx * tx;
291+
}
292+
293+
var s1 = desiredFrameLength;
294+
var t1 = 0;
295+
var t2 = (t_nom / t_denom) || 0.0;
296+
var s2 = desiredFrameLength - t2 * complexity;
297+
var n1 = bestIndex + 1;
298+
var n2 = sortedSamples.length - bestIndex - 1;
299+
300+
function getValueAt(at_complexity)
301+
{
302+
if (at_complexity > complexity)
303+
return s2 + t2 * complexity;
304+
return s1 + t1 * complexity;
305+
}
306+
307+
let error1 = 0.0;
308+
let error2 = 0.0;
309+
for (var i = 0; i < n1; ++i) {
310+
const frameLengthErr = sortedSamples[i][frameLengthIndex] - getValueAt(sortedSamples[i][complexityIndex]);
311+
error1 += frameLengthErr * frameLengthErr;
312+
}
313+
for (var i = n1; i < sortedSamples.length; ++i) {
314+
const frameLengthErr = sortedSamples[i][frameLengthIndex] - getValueAt(sortedSamples[i][complexityIndex]);
315+
error2 += frameLengthErr * frameLengthErr;
316+
}
317+
318+
return {
319+
s1: s1,
320+
t1: t1,
321+
s2: s2,
322+
t2: t2,
323+
complexity: complexity,
324+
// Number of samples included in the first segment, inclusive of bestIndex
325+
n1: n1,
326+
// Number of samples included in the second segment
327+
n2: n2,
328+
stdev1: Math.sqrt(error1 / n1),
329+
stdev2: Math.sqrt(error2 / n2),
330+
error: error1 + error2,
331+
};
332+
}
333+
222334
// A generic two-segment piecewise regression calculator. Based on Kundu/Ubhaya
223335
//
224336
// Minimize sum of (y - y')^2
@@ -250,7 +362,11 @@ class Regression {
250362
}
251363

252364
// Sort by increasing complexity.
253-
var sortedSamples = samples.slice().sort((a, b) => a[complexityIndex] - b[complexityIndex]);
365+
var sortedSamples = samples.slice().sort((a, b) => {
366+
if (a[complexityIndex] == b[complexityIndex])
367+
return 0;
368+
return a[complexityIndex] - b[complexityIndex]
369+
});
254370

255371
// x is expected to increase in complexity
256372
var lowComplexity = sortedSamples[0][complexityIndex];

MotionMark/resources/strings.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ var Strings = {
7878

7979
profiles: {
8080
slope: "slope",
81-
flat: "flat"
81+
flat: "flat",
82+
window: "window",
83+
windowStrict: "window-strict",
8284
},
8385

8486
results: {

MotionMark/tests/resources/controllers.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,15 @@ class Controller {
170170
{
171171
return samples[sampleTimeIndex][i] - samples[sampleTimeIndex][i - 1];
172172
}
173+
174+
_getFrameTime(samples, i)
175+
{
176+
return samples[sampleTimeIndex][i];
177+
}
173178

174179
_previousFrameComplexity(samples, i)
175180
{
176-
if (i > 0)
181+
if (i > 1)
177182
return this._getComplexity(samples, i - 1);
178183

179184
return 0;
@@ -399,6 +404,7 @@ class RampController extends Controller {
399404
super(benchmark, options);
400405

401406
this.targetFPS = targetFPS;
407+
this.preferredProfile = options["score-profile"];
402408

403409
// Initially start with a tier test to find the bounds
404410
// The number of objects in a tier test is 10^|_tier|
@@ -586,10 +592,15 @@ class RampController extends Controller {
586592
for (var i = this._rampStartIndex; i < this._sampler.sampleCount; ++i) {
587593
if (this._getFrameType(this._sampler.samples, i) == Strings.json.mutationFrameType)
588594
continue;
589-
regressionData.push([ this._getComplexity(this._sampler.samples, i), this._getFrameLength(this._sampler.samples, i) ]);
595+
regressionData.push(
596+
[
597+
this._getComplexity(this._sampler.samples, i),
598+
this._getFrameLength(this._sampler.samples, i),
599+
this._getFrameTime(this._sampler.samples, i)
600+
]);
590601
}
591602

592-
var regression = new Regression(regressionData, this._sampler.sampleCount - 1, this._rampStartIndex, { desiredFrameLength: this.frameLengthDesired });
603+
var regression = new Regression(regressionData, this._sampler.sampleCount - 1, this._rampStartIndex, { desiredFrameLength: this.frameLengthDesired, preferredProfile: this.preferredProfile });
593604
this._rampRegressions.push(regression);
594605

595606
var frameLengthAtMaxComplexity = regression.valueAt(this._maximumComplexity);

0 commit comments

Comments
 (0)