You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
480 lines
22 KiB
480 lines
22 KiB
<html> |
|
<head> |
|
<title>OO/decl benchmark</title> |
|
<style type="text/css"> |
|
@import "../../../dojo/resources/dojo.css"; |
|
|
|
.stats th, .stats td { padding: 3pt; } |
|
.stats th, .name { font-weight: bold; } |
|
table.stats, .stats th, .stats td { border: 1px solid lightgray; } |
|
.median { color: navy; font-weight: bold; } |
|
.fastest { background-color: #ccf; } |
|
.stablest { background-color: #ffc; } |
|
.fastest.stablest { background-color: #cfc; } |
|
</style> |
|
<script type="text/javascript" src="../../../dojo/dojo.js" data-dojo-config="isDebug:true"></script> |
|
<script type="text/javascript"> |
|
dojo.require("dojox.lang.tests.declare-old"); |
|
|
|
var d = dojo, oo = dojox.lang.oo, nothing = function(){}; |
|
|
|
// test harness |
|
|
|
var DELAY = 20, // pause in ms between tests |
|
LIMIT = 50, // the lower limit of a test |
|
COUNT = 50; // how many times to repeat the test |
|
|
|
// the basic unit to run a test with timing |
|
var runTest = function(f, n){ |
|
var start = new Date(); |
|
for(var i = 0; i < n; ++i){ |
|
f(); |
|
} |
|
var end = new Date(); |
|
return end.getTime() - start.getTime(); |
|
}; |
|
|
|
// find the threshold number of tests just exceeding the limit |
|
var findThreshold = function(f, limit){ |
|
// very simplistic search probing only powers of two |
|
var n = 1; |
|
while(runTest(f, n) + runTest(nothing, n) < limit) n <<= 1; |
|
return n; |
|
}; |
|
|
|
var _runUnitTest = function(a, f, n, k, m, next){ |
|
a[k++] = runTest(f, n) - runTest(nothing, n); |
|
if(k < m){ |
|
setTimeout(d.hitch(null, _runUnitTest, a, f, n, k, m, next), DELAY); |
|
}else{ |
|
next(a); |
|
} |
|
}; |
|
|
|
var runTests = function(f, n, m, next){ |
|
var a = new Array(m); |
|
_runUnitTest(a, f, n, 0, m, next); |
|
}; |
|
|
|
// statistics |
|
|
|
var statNames = ["minimum", "firstDecile", "lowerQuartile", "median", "upperQuartile", "lastDecile", "maximum", "average"]; |
|
|
|
var statAbbr = { |
|
minimum: "min", |
|
maximum: "max", |
|
median: "med", |
|
lowerQuartile: "25%", |
|
upperQuartile: "75%", |
|
firstDecile: "10%", |
|
lastDecile: "90%", |
|
average: "avg" |
|
}; |
|
|
|
var getWeightedValue = function(a, pos){ |
|
var p = pos * (a.length - 1), t = Math.ceil(p), f = t - 1; |
|
if(f <= 0){ return a[0]; } |
|
if(t >= a.length){ return a[a.length - 1]; } |
|
return a[f] * (t - p) + a[t] * (p - f); |
|
}; |
|
|
|
var getStats = function(a, n){ |
|
var t = a.slice(0); |
|
t.sort(function(a, b){ return a - b; }); |
|
var result = { |
|
// the five-number summary |
|
minimum: t[0], |
|
maximum: t[t.length - 1], |
|
median: getWeightedValue(t, 0.5), |
|
lowerQuartile: getWeightedValue(t, 0.25), |
|
upperQuartile: getWeightedValue(t, 0.75), |
|
// extended to the Bowley's seven-figure summary |
|
firstDecile: getWeightedValue(t, 0.1), |
|
lastDecile: getWeightedValue(t, 0.9) |
|
}; |
|
// add the average |
|
for(var i = 0, sum = 0; i < t.length; sum += t[i++]); |
|
result.average = sum / t.length; |
|
d.forEach(statNames, function(name){ |
|
if(result.hasOwnProperty(name) && typeof result[name] == "number"){ |
|
result[name] /= n; |
|
} |
|
}); |
|
return result; |
|
}; |
|
|
|
var testGroups = []; |
|
|
|
// run a group of tests, prepare statistics and show results |
|
|
|
var registerGroup = function(fs, bi, m, node, title){ |
|
var n = findThreshold(fs[bi].fun, LIMIT), |
|
x = { |
|
functions: fs, |
|
stats: [], |
|
process: function(a){ |
|
if(a){ |
|
this.stats.push(getStats(a, n)); |
|
console.log("test #" + this.stats.length + " is completed: " + this.functions[this.stats.length - 1].name); |
|
} |
|
if(this.stats.length < this.functions.length){ |
|
//setTimeout(d.hitch(null, runTests, this.functions[this.stats.length].fun, n, m, d.hitch(this, "process")), DELAY); |
|
var f = d.hitch(null, runTests, this.functions[this.stats.length].fun, n, m, d.hitch(this, "process")); |
|
f(); |
|
return; |
|
} |
|
var diff = Math.max.apply(Math, d.map(this.stats, function(s){ return s.upperQuartile - s.lowerQuartile; })), |
|
prec = 1 - Math.floor(Math.log(diff) / Math.LN10), fastest = 0, stablest = 0; |
|
d.forEach(this.stats, function(s, i){ |
|
if(i){ |
|
if(s.median < this.stats[fastest].median){ |
|
fastest = i; |
|
} |
|
if(s.upperQuartile - s.lowerQuartile < this.stats[i].upperQuartile - this.stats[i].lowerQuartile){ |
|
stablest = i; |
|
} |
|
} |
|
}, this); |
|
// add the table |
|
var tab = ["<table class='stats'><thead><tr><th>Test</th>"]; |
|
tab.push(d.map(this.functions, function(f, i){ |
|
return "<th class='" + (i == fastest ? "fastest" : "") + " " + (i == stablest ? "stablest" : "") + "'>" + f.name + "</th>"; |
|
}).join("")); |
|
tab.push("</tr></thead><tbody>"); |
|
d.forEach(statNames, function(n){ |
|
tab.push("<tr class='name " + n + "'><td>" + n + "</td>"); |
|
d.forEach(this.stats, function(s, i){ |
|
tab.push("<td class='" + (i == fastest ? "fastest" : "") + " " + (i == stablest ? "stablest" : "") + "'>" + s[n].toFixed(prec) + "</td>"); |
|
}, this); |
|
tab.push("</tr>"); |
|
}, this); |
|
tab.push("</tbody></table>"); |
|
d.place(tab.join(""), node); |
|
// next |
|
run(); |
|
} |
|
}; |
|
testGroups.push(function(){ |
|
console.log("all tests will be repeated " + n + " times in " + m + " series"); |
|
d.place("<h1>" + title + "</h1>", node); |
|
x.process(); |
|
}); |
|
}; |
|
|
|
function run(){ |
|
if(testGroups.length){ |
|
testGroups.shift()(); |
|
}else{ |
|
setTimeout(function(){ |
|
console.log("Done!"); |
|
alert("Done!"); |
|
}, DELAY); |
|
} |
|
} |
|
|
|
// actual benchmarks |
|
|
|
var decl0 = dojox.lang.tests.declareOld, declx = d.declare; |
|
|
|
var benchmarkClassCreation = function(){ |
|
var a0 = decl0("temp.A0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
b0 = decl0("temp.B0", a0, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
c0 = decl0("temp.C0", b0, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
|
|
ax = declx("temp.Ax", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
bx = declx("temp.Bx", ax, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
cx = declx("temp.Cx", bx, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
|
|
group = [ |
|
{ |
|
name: "old/A", |
|
fun: function(){ decl0("temp.test.A", null, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/A", |
|
fun: function(){ declx("temp.test.A", null, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "old/B", |
|
fun: function(){ decl0("temp.test.B", temp.A0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/B", |
|
fun: function(){ declx("temp.test.B", temp.Ax, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "old/C", |
|
fun: function(){ decl0("temp.test.C", temp.B0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/C", |
|
fun: function(){ declx("temp.test.C", temp.Bx, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "old/D", |
|
fun: function(){ decl0("temp.test.D", temp.C0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/D", |
|
fun: function(){ declx("temp.test.D", temp.Cx, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Create a class (single inheritance)"); |
|
}; |
|
|
|
var benchmarkClassMixing = function(){ |
|
var a0 = decl0("temp.A0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
b0 = decl0("temp.B0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
c0 = decl0("temp.C0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
|
|
ax = declx("temp.Ax", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
bx = declx("temp.Bx", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
cx = declx("temp.Cx", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
|
|
|
group = [ |
|
{ |
|
name: "old/A", |
|
fun: function(){ decl0("temp.test.A", temp.A0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/A", |
|
fun: function(){ declx("temp.test.A", temp.Ax, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "old/A,B", |
|
fun: function(){ decl0("temp.test.B", [temp.A0, temp.B0], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/A,B", |
|
fun: function(){ declx("temp.test.B", [temp.Ax, temp.Bx], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "old/A,B,C", |
|
fun: function(){ decl0("temp.test.C", [temp.A0, temp.B0, temp.C0], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
}, |
|
{ |
|
name: "new/A,B,C", |
|
fun: function(){ declx("temp.test.C", [temp.Ax, temp.Bx, temp.Cx], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Create a class with mixins"); |
|
}; |
|
|
|
var benchmarkConstructor = function(){ |
|
var A0 = decl0("temp.A0", null, {constructor: function(a){ this.a = a; }}), |
|
B0 = decl0("temp.B0", A0, {constructor: function(a, b){ this.b = b; }}), |
|
C0 = decl0("temp.C0", B0, {constructor: function(a, b, c){ this.c = c; }}), |
|
D0 = decl0("temp.D0", C0, {constructor: function(a, b, c, d){ this.d = d; }}), |
|
|
|
Ax = declx("temp.Ax", null, {constructor: function(a){ this.a = a; }}), |
|
Bx = declx("temp.Bx", Ax, {constructor: function(a, b){ this.b = b; }}), |
|
Cx = declx("temp.Cx", Bx, {constructor: function(a, b, c){ this.c = c; }}), |
|
Dx = declx("temp.Dx", Cx, {constructor: function(a, b, c, d){ this.d = d; }}), |
|
|
|
group = [ |
|
{ |
|
name: "old/A", |
|
fun: function(){ var t = new A0("a"); } |
|
}, |
|
{ |
|
name: "new/A", |
|
fun: function(){ var t = new Ax("a"); } |
|
}, |
|
{ |
|
name: "old/B", |
|
fun: function(){ var t = new B0("a", "b"); } |
|
}, |
|
{ |
|
name: "new/B", |
|
fun: function(){ var t = new Bx("a", "b"); } |
|
}, |
|
{ |
|
name: "old/C", |
|
fun: function(){ var t = new C0("a", "b", "c"); } |
|
}, |
|
{ |
|
name: "new/C", |
|
fun: function(){ var t = new Cx("a", "b", "c"); } |
|
}, |
|
{ |
|
name: "old/D", |
|
fun: function(){ var t = new D0("a", "b", "c", "d"); } |
|
}, |
|
{ |
|
name: "new/D", |
|
fun: function(){ var t = new Dx("a", "b", "c", "d"); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Create an instance"); |
|
}; |
|
|
|
var benchmarkRegularCalls = function(){ |
|
var A0 = decl0("temp.A0", null, {ma: function(a){ return this.a = a; }}), |
|
B0 = decl0("temp.B0", A0, {mb: function(b){ return this.b = b; }}), |
|
C0 = decl0("temp.C0", B0, {mc: function(c){ return this.c = c; }}), |
|
D0 = decl0("temp.D0", C0, {md: function(d){ return this.d = d; }}), |
|
|
|
Ax = declx("temp.Ax", null, {ma: function(a){ return this.a = a; }}), |
|
Bx = declx("temp.Bx", Ax, {mb: function(b){ return this.b = b; }}), |
|
Cx = declx("temp.Cx", Bx, {mc: function(c){ return this.c = c; }}), |
|
Dx = declx("temp.Dx", Cx, {md: function(d){ return this.d = d; }}), |
|
|
|
d0 = new D0, |
|
dx = new Dx, |
|
|
|
group = [ |
|
{ |
|
name: "old/A", |
|
fun: function(){ d0.ma("x"); } |
|
}, |
|
{ |
|
name: "new/A", |
|
fun: function(){ dx.ma("x"); } |
|
}, |
|
{ |
|
name: "old/B", |
|
fun: function(){ d0.mb("x"); } |
|
}, |
|
{ |
|
name: "new/B", |
|
fun: function(){ dx.mb("x"); } |
|
}, |
|
{ |
|
name: "old/C", |
|
fun: function(){ d0.mc("x"); } |
|
}, |
|
{ |
|
name: "new/C", |
|
fun: function(){ dx.mc("x"); } |
|
}, |
|
{ |
|
name: "old/D", |
|
fun: function(){ d0.md("x"); } |
|
}, |
|
{ |
|
name: "new/D", |
|
fun: function(){ dx.md("x"); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Call a method"); |
|
}; |
|
|
|
var benchmarkInheritedCalls = function(){ |
|
var A0 = decl0("temp.A0", null, {m: function(a){ return this.a = a; }}), |
|
B0 = decl0("temp.B0", A0, {m: function(b){ return this.b = this.inherited(arguments); }}), |
|
C0 = decl0("temp.C0", B0, {m: function(c){ return this.c = this.inherited(arguments); }}), |
|
D0 = decl0("temp.D0", C0, {m: function(d){ return this.d = this.inherited(arguments); }}), |
|
|
|
Ax = declx("temp.Ax", null, {m: function(a){ return this.a = a; }}), |
|
Bx = declx("temp.Bx", Ax, {m: function(b){ return this.b = this.inherited(arguments); }}), |
|
Cx = declx("temp.Cx", Bx, {m: function(c){ return this.c = this.inherited(arguments); }}), |
|
Dx = declx("temp.Dx", Cx, {m: function(d){ return this.d = this.inherited(arguments); }}), |
|
|
|
b0 = new B0, |
|
bx = new Bx, |
|
|
|
c0 = new C0, |
|
cx = new Cx, |
|
|
|
d0 = new D0, |
|
dx = new Dx, |
|
|
|
group = [ |
|
{ |
|
name: "old/B", |
|
fun: function(){ b0.m("x"); } |
|
}, |
|
{ |
|
name: "new/B", |
|
fun: function(){ bx.m("x"); } |
|
}, |
|
{ |
|
name: "old/C", |
|
fun: function(){ c0.m("x"); } |
|
}, |
|
{ |
|
name: "new/C", |
|
fun: function(){ cx.m("x"); } |
|
}, |
|
{ |
|
name: "old/D", |
|
fun: function(){ d0.m("x"); } |
|
}, |
|
{ |
|
name: "new/D", |
|
fun: function(){ dx.m("x"); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Call an inherited method"); |
|
}; |
|
|
|
var benchmarkChains = function(){ |
|
var A0 = decl0("temp.A0", null, {m: function(a){ this.a = a; }}), |
|
B0 = decl0("temp.B0", A0, {m: function(b){ this.inherited(arguments); this.b = b; }}), |
|
C0 = decl0("temp.C0", B0, {m: function(c){ this.inherited(arguments); this.c = c; }}), |
|
D0 = decl0("temp.D0", C0, {m: function(d){ this.inherited(arguments); this.d = d; }}), |
|
|
|
Ax = declx("temp.Ax", null, {m: function(a){ this.a = a; }, "-chains-": {m: "after"}}), |
|
Bx = declx("temp.Bx", Ax, {m: function(b){ this.b = b; }}), |
|
Cx = declx("temp.Cx", Bx, {m: function(c){ this.c = c; }}), |
|
Dx = declx("temp.Dx", Cx, {m: function(d){ this.d = d; }}), |
|
|
|
b0 = new B0, |
|
bx = new Bx, |
|
|
|
c0 = new C0, |
|
cx = new Cx, |
|
|
|
d0 = new D0, |
|
dx = new Dx, |
|
|
|
group = [ |
|
{ |
|
name: "old/B", |
|
fun: function(){ b0.m("x"); } |
|
}, |
|
{ |
|
name: "new/B", |
|
fun: function(){ bx.m("x"); } |
|
}, |
|
{ |
|
name: "old/C", |
|
fun: function(){ c0.m("x"); } |
|
}, |
|
{ |
|
name: "new/C", |
|
fun: function(){ cx.m("x"); } |
|
}, |
|
{ |
|
name: "old/D", |
|
fun: function(){ d0.m("x"); } |
|
}, |
|
{ |
|
name: "new/D", |
|
fun: function(){ dx.m("x"); } |
|
} |
|
]; |
|
registerGroup(group, 0, COUNT, "result", "Call a chain"); |
|
}; |
|
|
|
var startBenchmarks = function(){ |
|
console.log("Used parameters: count=" + COUNT + " limit=" + LIMIT + "ms delay=" + DELAY + "ms"); |
|
benchmarkClassCreation(); |
|
benchmarkClassMixing(); |
|
benchmarkConstructor(); |
|
//benchmarkRegularCalls(); |
|
benchmarkInheritedCalls(); |
|
benchmarkChains(); |
|
run(); |
|
}; |
|
|
|
//dojo.addOnLoad(startBenchmarks); |
|
</script> |
|
</head> |
|
<body> |
|
<p>Warning: the benchmark takes several minutes, wait for a dialog box.</p> |
|
<p>Color legend: <span class="fastest">the fastest</span>, <span class="stablest">the most stable</span>, <span class="fastest stablest">the fastest and the most stable</span></p> |
|
<p><button onclick="startBenchmarks()">Start</button></p> |
|
<div id="result"></div> |
|
</body> |
|
</html>
|
|
|