1 | <html> |
---|
2 | <head> |
---|
3 | <title>OO/decl benchmark</title> |
---|
4 | <style type="text/css"> |
---|
5 | @import "../../../dojo/resources/dojo.css"; |
---|
6 | |
---|
7 | .stats th, .stats td { padding: 3pt; } |
---|
8 | .stats th, .name { font-weight: bold; } |
---|
9 | table.stats, .stats th, .stats td { border: 1px solid lightgray; } |
---|
10 | .median { color: navy; font-weight: bold; } |
---|
11 | .fastest { background-color: #ccf; } |
---|
12 | .stablest { background-color: #ffc; } |
---|
13 | .fastest.stablest { background-color: #cfc; } |
---|
14 | </style> |
---|
15 | <script type="text/javascript" src="../../../dojo/dojo.js" data-dojo-config="isDebug:true"></script> |
---|
16 | <script type="text/javascript"> |
---|
17 | dojo.require("dojox.lang.tests.declare-old"); |
---|
18 | |
---|
19 | var d = dojo, oo = dojox.lang.oo, nothing = function(){}; |
---|
20 | |
---|
21 | // test harness |
---|
22 | |
---|
23 | var DELAY = 20, // pause in ms between tests |
---|
24 | LIMIT = 50, // the lower limit of a test |
---|
25 | COUNT = 50; // how many times to repeat the test |
---|
26 | |
---|
27 | // the basic unit to run a test with timing |
---|
28 | var runTest = function(f, n){ |
---|
29 | var start = new Date(); |
---|
30 | for(var i = 0; i < n; ++i){ |
---|
31 | f(); |
---|
32 | } |
---|
33 | var end = new Date(); |
---|
34 | return end.getTime() - start.getTime(); |
---|
35 | }; |
---|
36 | |
---|
37 | // find the threshold number of tests just exceeding the limit |
---|
38 | var findThreshold = function(f, limit){ |
---|
39 | // very simplistic search probing only powers of two |
---|
40 | var n = 1; |
---|
41 | while(runTest(f, n) + runTest(nothing, n) < limit) n <<= 1; |
---|
42 | return n; |
---|
43 | }; |
---|
44 | |
---|
45 | var _runUnitTest = function(a, f, n, k, m, next){ |
---|
46 | a[k++] = runTest(f, n) - runTest(nothing, n); |
---|
47 | if(k < m){ |
---|
48 | setTimeout(d.hitch(null, _runUnitTest, a, f, n, k, m, next), DELAY); |
---|
49 | }else{ |
---|
50 | next(a); |
---|
51 | } |
---|
52 | }; |
---|
53 | |
---|
54 | var runTests = function(f, n, m, next){ |
---|
55 | var a = new Array(m); |
---|
56 | _runUnitTest(a, f, n, 0, m, next); |
---|
57 | }; |
---|
58 | |
---|
59 | // statistics |
---|
60 | |
---|
61 | var statNames = ["minimum", "firstDecile", "lowerQuartile", "median", "upperQuartile", "lastDecile", "maximum", "average"]; |
---|
62 | |
---|
63 | var statAbbr = { |
---|
64 | minimum: "min", |
---|
65 | maximum: "max", |
---|
66 | median: "med", |
---|
67 | lowerQuartile: "25%", |
---|
68 | upperQuartile: "75%", |
---|
69 | firstDecile: "10%", |
---|
70 | lastDecile: "90%", |
---|
71 | average: "avg" |
---|
72 | }; |
---|
73 | |
---|
74 | var getWeightedValue = function(a, pos){ |
---|
75 | var p = pos * (a.length - 1), t = Math.ceil(p), f = t - 1; |
---|
76 | if(f <= 0){ return a[0]; } |
---|
77 | if(t >= a.length){ return a[a.length - 1]; } |
---|
78 | return a[f] * (t - p) + a[t] * (p - f); |
---|
79 | }; |
---|
80 | |
---|
81 | var getStats = function(a, n){ |
---|
82 | var t = a.slice(0); |
---|
83 | t.sort(function(a, b){ return a - b; }); |
---|
84 | var result = { |
---|
85 | // the five-number summary |
---|
86 | minimum: t[0], |
---|
87 | maximum: t[t.length - 1], |
---|
88 | median: getWeightedValue(t, 0.5), |
---|
89 | lowerQuartile: getWeightedValue(t, 0.25), |
---|
90 | upperQuartile: getWeightedValue(t, 0.75), |
---|
91 | // extended to the Bowley's seven-figure summary |
---|
92 | firstDecile: getWeightedValue(t, 0.1), |
---|
93 | lastDecile: getWeightedValue(t, 0.9) |
---|
94 | }; |
---|
95 | // add the average |
---|
96 | for(var i = 0, sum = 0; i < t.length; sum += t[i++]); |
---|
97 | result.average = sum / t.length; |
---|
98 | d.forEach(statNames, function(name){ |
---|
99 | if(result.hasOwnProperty(name) && typeof result[name] == "number"){ |
---|
100 | result[name] /= n; |
---|
101 | } |
---|
102 | }); |
---|
103 | return result; |
---|
104 | }; |
---|
105 | |
---|
106 | var testGroups = []; |
---|
107 | |
---|
108 | // run a group of tests, prepare statistics and show results |
---|
109 | |
---|
110 | var registerGroup = function(fs, bi, m, node, title){ |
---|
111 | var n = findThreshold(fs[bi].fun, LIMIT), |
---|
112 | x = { |
---|
113 | functions: fs, |
---|
114 | stats: [], |
---|
115 | process: function(a){ |
---|
116 | if(a){ |
---|
117 | this.stats.push(getStats(a, n)); |
---|
118 | console.log("test #" + this.stats.length + " is completed: " + this.functions[this.stats.length - 1].name); |
---|
119 | } |
---|
120 | if(this.stats.length < this.functions.length){ |
---|
121 | //setTimeout(d.hitch(null, runTests, this.functions[this.stats.length].fun, n, m, d.hitch(this, "process")), DELAY); |
---|
122 | var f = d.hitch(null, runTests, this.functions[this.stats.length].fun, n, m, d.hitch(this, "process")); |
---|
123 | f(); |
---|
124 | return; |
---|
125 | } |
---|
126 | var diff = Math.max.apply(Math, d.map(this.stats, function(s){ return s.upperQuartile - s.lowerQuartile; })), |
---|
127 | prec = 1 - Math.floor(Math.log(diff) / Math.LN10), fastest = 0, stablest = 0; |
---|
128 | d.forEach(this.stats, function(s, i){ |
---|
129 | if(i){ |
---|
130 | if(s.median < this.stats[fastest].median){ |
---|
131 | fastest = i; |
---|
132 | } |
---|
133 | if(s.upperQuartile - s.lowerQuartile < this.stats[i].upperQuartile - this.stats[i].lowerQuartile){ |
---|
134 | stablest = i; |
---|
135 | } |
---|
136 | } |
---|
137 | }, this); |
---|
138 | // add the table |
---|
139 | var tab = ["<table class='stats'><thead><tr><th>Test</th>"]; |
---|
140 | tab.push(d.map(this.functions, function(f, i){ |
---|
141 | return "<th class='" + (i == fastest ? "fastest" : "") + " " + (i == stablest ? "stablest" : "") + "'>" + f.name + "</th>"; |
---|
142 | }).join("")); |
---|
143 | tab.push("</tr></thead><tbody>"); |
---|
144 | d.forEach(statNames, function(n){ |
---|
145 | tab.push("<tr class='name " + n + "'><td>" + n + "</td>"); |
---|
146 | d.forEach(this.stats, function(s, i){ |
---|
147 | tab.push("<td class='" + (i == fastest ? "fastest" : "") + " " + (i == stablest ? "stablest" : "") + "'>" + s[n].toFixed(prec) + "</td>"); |
---|
148 | }, this); |
---|
149 | tab.push("</tr>"); |
---|
150 | }, this); |
---|
151 | tab.push("</tbody></table>"); |
---|
152 | d.place(tab.join(""), node); |
---|
153 | // next |
---|
154 | run(); |
---|
155 | } |
---|
156 | }; |
---|
157 | testGroups.push(function(){ |
---|
158 | console.log("all tests will be repeated " + n + " times in " + m + " series"); |
---|
159 | d.place("<h1>" + title + "</h1>", node); |
---|
160 | x.process(); |
---|
161 | }); |
---|
162 | }; |
---|
163 | |
---|
164 | function run(){ |
---|
165 | if(testGroups.length){ |
---|
166 | testGroups.shift()(); |
---|
167 | }else{ |
---|
168 | setTimeout(function(){ |
---|
169 | console.log("Done!"); |
---|
170 | alert("Done!"); |
---|
171 | }, DELAY); |
---|
172 | } |
---|
173 | } |
---|
174 | |
---|
175 | // actual benchmarks |
---|
176 | |
---|
177 | var decl0 = dojox.lang.tests.declareOld, declx = d.declare; |
---|
178 | |
---|
179 | var benchmarkClassCreation = function(){ |
---|
180 | var a0 = decl0("temp.A0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
181 | b0 = decl0("temp.B0", a0, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
182 | c0 = decl0("temp.C0", b0, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
183 | |
---|
184 | ax = declx("temp.Ax", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
185 | bx = declx("temp.Bx", ax, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
186 | cx = declx("temp.Cx", bx, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
187 | |
---|
188 | group = [ |
---|
189 | { |
---|
190 | name: "old/A", |
---|
191 | fun: function(){ decl0("temp.test.A", null, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
192 | }, |
---|
193 | { |
---|
194 | name: "new/A", |
---|
195 | fun: function(){ declx("temp.test.A", null, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
196 | }, |
---|
197 | { |
---|
198 | name: "old/B", |
---|
199 | fun: function(){ decl0("temp.test.B", temp.A0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
200 | }, |
---|
201 | { |
---|
202 | name: "new/B", |
---|
203 | fun: function(){ declx("temp.test.B", temp.Ax, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
204 | }, |
---|
205 | { |
---|
206 | name: "old/C", |
---|
207 | fun: function(){ decl0("temp.test.C", temp.B0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
208 | }, |
---|
209 | { |
---|
210 | name: "new/C", |
---|
211 | fun: function(){ declx("temp.test.C", temp.Bx, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
212 | }, |
---|
213 | { |
---|
214 | name: "old/D", |
---|
215 | fun: function(){ decl0("temp.test.D", temp.C0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
216 | }, |
---|
217 | { |
---|
218 | name: "new/D", |
---|
219 | fun: function(){ declx("temp.test.D", temp.Cx, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
220 | } |
---|
221 | ]; |
---|
222 | registerGroup(group, 0, COUNT, "result", "Create a class (single inheritance)"); |
---|
223 | }; |
---|
224 | |
---|
225 | var benchmarkClassMixing = function(){ |
---|
226 | var a0 = decl0("temp.A0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
227 | b0 = decl0("temp.B0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
228 | c0 = decl0("temp.C0", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
229 | |
---|
230 | ax = declx("temp.Ax", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
231 | bx = declx("temp.Bx", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
232 | cx = declx("temp.Cx", null, {m1: function(){}, m2: function(){}, m3: function(){}}), |
---|
233 | |
---|
234 | group = [ |
---|
235 | { |
---|
236 | name: "old/A", |
---|
237 | fun: function(){ decl0("temp.test.A", temp.A0, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
238 | }, |
---|
239 | { |
---|
240 | name: "new/A", |
---|
241 | fun: function(){ declx("temp.test.A", temp.Ax, {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
242 | }, |
---|
243 | { |
---|
244 | name: "old/A,B", |
---|
245 | fun: function(){ decl0("temp.test.B", [temp.A0, temp.B0], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
246 | }, |
---|
247 | { |
---|
248 | name: "new/A,B", |
---|
249 | fun: function(){ declx("temp.test.B", [temp.Ax, temp.Bx], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
250 | }, |
---|
251 | { |
---|
252 | name: "old/A,B,C", |
---|
253 | fun: function(){ decl0("temp.test.C", [temp.A0, temp.B0, temp.C0], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
254 | }, |
---|
255 | { |
---|
256 | name: "new/A,B,C", |
---|
257 | fun: function(){ declx("temp.test.C", [temp.Ax, temp.Bx, temp.Cx], {m1: function(){}, m2: function(){}, m3: function(){}}); } |
---|
258 | } |
---|
259 | ]; |
---|
260 | registerGroup(group, 0, COUNT, "result", "Create a class with mixins"); |
---|
261 | }; |
---|
262 | |
---|
263 | var benchmarkConstructor = function(){ |
---|
264 | var A0 = decl0("temp.A0", null, {constructor: function(a){ this.a = a; }}), |
---|
265 | B0 = decl0("temp.B0", A0, {constructor: function(a, b){ this.b = b; }}), |
---|
266 | C0 = decl0("temp.C0", B0, {constructor: function(a, b, c){ this.c = c; }}), |
---|
267 | D0 = decl0("temp.D0", C0, {constructor: function(a, b, c, d){ this.d = d; }}), |
---|
268 | |
---|
269 | Ax = declx("temp.Ax", null, {constructor: function(a){ this.a = a; }}), |
---|
270 | Bx = declx("temp.Bx", Ax, {constructor: function(a, b){ this.b = b; }}), |
---|
271 | Cx = declx("temp.Cx", Bx, {constructor: function(a, b, c){ this.c = c; }}), |
---|
272 | Dx = declx("temp.Dx", Cx, {constructor: function(a, b, c, d){ this.d = d; }}), |
---|
273 | |
---|
274 | group = [ |
---|
275 | { |
---|
276 | name: "old/A", |
---|
277 | fun: function(){ var t = new A0("a"); } |
---|
278 | }, |
---|
279 | { |
---|
280 | name: "new/A", |
---|
281 | fun: function(){ var t = new Ax("a"); } |
---|
282 | }, |
---|
283 | { |
---|
284 | name: "old/B", |
---|
285 | fun: function(){ var t = new B0("a", "b"); } |
---|
286 | }, |
---|
287 | { |
---|
288 | name: "new/B", |
---|
289 | fun: function(){ var t = new Bx("a", "b"); } |
---|
290 | }, |
---|
291 | { |
---|
292 | name: "old/C", |
---|
293 | fun: function(){ var t = new C0("a", "b", "c"); } |
---|
294 | }, |
---|
295 | { |
---|
296 | name: "new/C", |
---|
297 | fun: function(){ var t = new Cx("a", "b", "c"); } |
---|
298 | }, |
---|
299 | { |
---|
300 | name: "old/D", |
---|
301 | fun: function(){ var t = new D0("a", "b", "c", "d"); } |
---|
302 | }, |
---|
303 | { |
---|
304 | name: "new/D", |
---|
305 | fun: function(){ var t = new Dx("a", "b", "c", "d"); } |
---|
306 | } |
---|
307 | ]; |
---|
308 | registerGroup(group, 0, COUNT, "result", "Create an instance"); |
---|
309 | }; |
---|
310 | |
---|
311 | var benchmarkRegularCalls = function(){ |
---|
312 | var A0 = decl0("temp.A0", null, {ma: function(a){ return this.a = a; }}), |
---|
313 | B0 = decl0("temp.B0", A0, {mb: function(b){ return this.b = b; }}), |
---|
314 | C0 = decl0("temp.C0", B0, {mc: function(c){ return this.c = c; }}), |
---|
315 | D0 = decl0("temp.D0", C0, {md: function(d){ return this.d = d; }}), |
---|
316 | |
---|
317 | Ax = declx("temp.Ax", null, {ma: function(a){ return this.a = a; }}), |
---|
318 | Bx = declx("temp.Bx", Ax, {mb: function(b){ return this.b = b; }}), |
---|
319 | Cx = declx("temp.Cx", Bx, {mc: function(c){ return this.c = c; }}), |
---|
320 | Dx = declx("temp.Dx", Cx, {md: function(d){ return this.d = d; }}), |
---|
321 | |
---|
322 | d0 = new D0, |
---|
323 | dx = new Dx, |
---|
324 | |
---|
325 | group = [ |
---|
326 | { |
---|
327 | name: "old/A", |
---|
328 | fun: function(){ d0.ma("x"); } |
---|
329 | }, |
---|
330 | { |
---|
331 | name: "new/A", |
---|
332 | fun: function(){ dx.ma("x"); } |
---|
333 | }, |
---|
334 | { |
---|
335 | name: "old/B", |
---|
336 | fun: function(){ d0.mb("x"); } |
---|
337 | }, |
---|
338 | { |
---|
339 | name: "new/B", |
---|
340 | fun: function(){ dx.mb("x"); } |
---|
341 | }, |
---|
342 | { |
---|
343 | name: "old/C", |
---|
344 | fun: function(){ d0.mc("x"); } |
---|
345 | }, |
---|
346 | { |
---|
347 | name: "new/C", |
---|
348 | fun: function(){ dx.mc("x"); } |
---|
349 | }, |
---|
350 | { |
---|
351 | name: "old/D", |
---|
352 | fun: function(){ d0.md("x"); } |
---|
353 | }, |
---|
354 | { |
---|
355 | name: "new/D", |
---|
356 | fun: function(){ dx.md("x"); } |
---|
357 | } |
---|
358 | ]; |
---|
359 | registerGroup(group, 0, COUNT, "result", "Call a method"); |
---|
360 | }; |
---|
361 | |
---|
362 | var benchmarkInheritedCalls = function(){ |
---|
363 | var A0 = decl0("temp.A0", null, {m: function(a){ return this.a = a; }}), |
---|
364 | B0 = decl0("temp.B0", A0, {m: function(b){ return this.b = this.inherited(arguments); }}), |
---|
365 | C0 = decl0("temp.C0", B0, {m: function(c){ return this.c = this.inherited(arguments); }}), |
---|
366 | D0 = decl0("temp.D0", C0, {m: function(d){ return this.d = this.inherited(arguments); }}), |
---|
367 | |
---|
368 | Ax = declx("temp.Ax", null, {m: function(a){ return this.a = a; }}), |
---|
369 | Bx = declx("temp.Bx", Ax, {m: function(b){ return this.b = this.inherited(arguments); }}), |
---|
370 | Cx = declx("temp.Cx", Bx, {m: function(c){ return this.c = this.inherited(arguments); }}), |
---|
371 | Dx = declx("temp.Dx", Cx, {m: function(d){ return this.d = this.inherited(arguments); }}), |
---|
372 | |
---|
373 | b0 = new B0, |
---|
374 | bx = new Bx, |
---|
375 | |
---|
376 | c0 = new C0, |
---|
377 | cx = new Cx, |
---|
378 | |
---|
379 | d0 = new D0, |
---|
380 | dx = new Dx, |
---|
381 | |
---|
382 | group = [ |
---|
383 | { |
---|
384 | name: "old/B", |
---|
385 | fun: function(){ b0.m("x"); } |
---|
386 | }, |
---|
387 | { |
---|
388 | name: "new/B", |
---|
389 | fun: function(){ bx.m("x"); } |
---|
390 | }, |
---|
391 | { |
---|
392 | name: "old/C", |
---|
393 | fun: function(){ c0.m("x"); } |
---|
394 | }, |
---|
395 | { |
---|
396 | name: "new/C", |
---|
397 | fun: function(){ cx.m("x"); } |
---|
398 | }, |
---|
399 | { |
---|
400 | name: "old/D", |
---|
401 | fun: function(){ d0.m("x"); } |
---|
402 | }, |
---|
403 | { |
---|
404 | name: "new/D", |
---|
405 | fun: function(){ dx.m("x"); } |
---|
406 | } |
---|
407 | ]; |
---|
408 | registerGroup(group, 0, COUNT, "result", "Call an inherited method"); |
---|
409 | }; |
---|
410 | |
---|
411 | var benchmarkChains = function(){ |
---|
412 | var A0 = decl0("temp.A0", null, {m: function(a){ this.a = a; }}), |
---|
413 | B0 = decl0("temp.B0", A0, {m: function(b){ this.inherited(arguments); this.b = b; }}), |
---|
414 | C0 = decl0("temp.C0", B0, {m: function(c){ this.inherited(arguments); this.c = c; }}), |
---|
415 | D0 = decl0("temp.D0", C0, {m: function(d){ this.inherited(arguments); this.d = d; }}), |
---|
416 | |
---|
417 | Ax = declx("temp.Ax", null, {m: function(a){ this.a = a; }, "-chains-": {m: "after"}}), |
---|
418 | Bx = declx("temp.Bx", Ax, {m: function(b){ this.b = b; }}), |
---|
419 | Cx = declx("temp.Cx", Bx, {m: function(c){ this.c = c; }}), |
---|
420 | Dx = declx("temp.Dx", Cx, {m: function(d){ this.d = d; }}), |
---|
421 | |
---|
422 | b0 = new B0, |
---|
423 | bx = new Bx, |
---|
424 | |
---|
425 | c0 = new C0, |
---|
426 | cx = new Cx, |
---|
427 | |
---|
428 | d0 = new D0, |
---|
429 | dx = new Dx, |
---|
430 | |
---|
431 | group = [ |
---|
432 | { |
---|
433 | name: "old/B", |
---|
434 | fun: function(){ b0.m("x"); } |
---|
435 | }, |
---|
436 | { |
---|
437 | name: "new/B", |
---|
438 | fun: function(){ bx.m("x"); } |
---|
439 | }, |
---|
440 | { |
---|
441 | name: "old/C", |
---|
442 | fun: function(){ c0.m("x"); } |
---|
443 | }, |
---|
444 | { |
---|
445 | name: "new/C", |
---|
446 | fun: function(){ cx.m("x"); } |
---|
447 | }, |
---|
448 | { |
---|
449 | name: "old/D", |
---|
450 | fun: function(){ d0.m("x"); } |
---|
451 | }, |
---|
452 | { |
---|
453 | name: "new/D", |
---|
454 | fun: function(){ dx.m("x"); } |
---|
455 | } |
---|
456 | ]; |
---|
457 | registerGroup(group, 0, COUNT, "result", "Call a chain"); |
---|
458 | }; |
---|
459 | |
---|
460 | var startBenchmarks = function(){ |
---|
461 | console.log("Used parameters: count=" + COUNT + " limit=" + LIMIT + "ms delay=" + DELAY + "ms"); |
---|
462 | benchmarkClassCreation(); |
---|
463 | benchmarkClassMixing(); |
---|
464 | benchmarkConstructor(); |
---|
465 | //benchmarkRegularCalls(); |
---|
466 | benchmarkInheritedCalls(); |
---|
467 | benchmarkChains(); |
---|
468 | run(); |
---|
469 | }; |
---|
470 | |
---|
471 | //dojo.addOnLoad(startBenchmarks); |
---|
472 | </script> |
---|
473 | </head> |
---|
474 | <body> |
---|
475 | <p>Warning: the benchmark takes several minutes, wait for a dialog box.</p> |
---|
476 | <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> |
---|
477 | <p><button onclick="startBenchmarks()">Start</button></p> |
---|
478 | <div id="result"></div> |
---|
479 | </body> |
---|
480 | </html> |
---|