-
-
Save WebReflection/5aaca727bc3b784d43be3704cf65abff to your computer and use it in GitHub Desktop.
// Players | |
class ClickCounter { | |
constructor() { this.clicks = 0; } | |
onclick(e) { this.clicks += (e.type === 'click') ? 1 : -1; } | |
} | |
class Handler extends ClickCounter { | |
constructor(currentTarget) { | |
super(); | |
currentTarget.addEventListener('click', this); | |
} | |
} | |
class DynamicHandler extends Handler { | |
handleEvent(e) { this['on' + e.type](e); } | |
} | |
class StaticHandler extends Handler { | |
handleEvent(e) { switch (e.type) { | |
case 'click': return this.onclick(e); | |
}} | |
} | |
// just to rule out hierarchy performance | |
class Bounder extends ClickCounter { | |
constructor(currentTarget) { super(); } | |
} | |
class ArrowHandler extends Bounder { | |
constructor(currentTarget) { | |
super(currentTarget); | |
this.click = (e) => this.onclick(e); | |
currentTarget.addEventListener('click', this.click); | |
} | |
} | |
class BoundHandler extends Bounder { | |
constructor(currentTarget) { | |
super(currentTarget); | |
this.onclick = this.onclick.bind(this); | |
currentTarget.addEventListener('click', this.onclick); | |
} | |
} | |
// Rules | |
const benchmark = (Class, length = 1000, samples = 5) => { | |
const currentTarget = button(Class.name); | |
const instances = new Array(length); | |
return new Promise(res => setTimeout(res, 500)).then(() => { | |
let benchName; | |
let memory; | |
benchName = `new ${Class.name}(currentTarget)`; | |
memory = performance.memory.usedJSHeapSize; | |
console.time(benchName); | |
for (let i = 0; i < length; i++) | |
instances[i] = new Class(currentTarget); | |
console.timeEnd(benchName); | |
memory = performance.memory.usedJSHeapSize - memory; | |
if (memory) console.log('memory: ', memory); | |
const event = new Event('click'); | |
benchName = 'currentTarget.dispatchEvent(clickEvent)'; | |
memory = performance.memory.usedJSHeapSize; | |
console.time(benchName); | |
for (let i = 0; i < samples; i++) | |
currentTarget.dispatchEvent(event); | |
console.timeEnd(benchName); | |
memory = performance.memory.usedJSHeapSize - memory; | |
if (memory) console.log('memory: ', memory); | |
console.assert( | |
instances.every(instance => instance.clicks === samples), | |
`expected ${length} clicks, got ${instances[0].clicks} instead` | |
); | |
}); | |
}; | |
// Helpers | |
const button = textContent => { | |
const el = document.createElement('button'); | |
el.textContent = textContent; | |
return document.body.appendChild(el); | |
}; | |
// Race ! | |
var instances = 10000; // how many instances ? | |
var dispatches = 10; // how many dispatches ? | |
Promise | |
.resolve() | |
.then(() => benchmark(DynamicHandler, instances, dispatches)) | |
.then(() => benchmark(StaticHandler, instances, dispatches)) | |
.then(() => benchmark(ArrowHandler, instances, dispatches)) | |
.then(() => benchmark(BoundHandler, instances, dispatches)) | |
; |
WebReflection
commented
Jun 17, 2017
Alternative with perf-monitor
(function (script) {
script.src = 'https://unpkg.com/[email protected]/dist/umd/perf-monitor.js';
script.onload = function () {
perfMonitor.startMemMonitor();
var instances = 1000; // how many instances ?
var dispatches = 10; // how many dispatches ?
var all = [
DynamicHandler,
StaticHandler,
ArrowHandler,
BoundHandler
];
all.forEach(Class => {
perfMonitor.initProfiler(Class.name);
});
(function bench() {
all.reduce((previus, Class) =>
previus.then(() => benchmark(Class, instances, dispatches)),
Promise.resolve()
).then(bench);
}());
};
}(
document.head.appendChild(
document.createElement('script')
)
));
// Players
class ClickCounter {
constructor() { this.clicks = 0; }
onclick(e) { this.clicks += (e.type === 'click') ? 1 : -1; }
}
class Handler extends ClickCounter {
constructor(currentTarget) {
super();
currentTarget.addEventListener('click', this);
}
}
class DynamicHandler extends Handler {
handleEvent(e) { this['on' + e.type](e); }
}
class StaticHandler extends Handler {
handleEvent(e) { switch (e.type) {
case 'click': return this.onclick(e);
}}
}
// just to rule out hierarchy performance
class Bounder extends ClickCounter {
constructor(currentTarget) { super(); }
}
class ArrowHandler extends Bounder {
constructor(currentTarget) {
super(currentTarget);
this.click = (e) => this.onclick(e);
currentTarget.addEventListener('click', this.click);
}
}
class BoundHandler extends Bounder {
constructor(currentTarget) {
super(currentTarget);
this.onclick = this.onclick.bind(this);
currentTarget.addEventListener('click', this.onclick);
}
}
// Rules
const benchmark = (Class, length = 1000, samples = 5) => {
const currentTarget = button(Class.name);
const instances = new Array(length);
return new Promise(res => {
requestAnimationFrame(() => {
perfMonitor.startProfile(Class.name);
for (let i = 0; i < length; i++)
instances[i] = new Class(currentTarget);
const event = new Event('click');
for (let i = 0; i < samples; i++)
currentTarget.dispatchEvent(event);
perfMonitor.endProfile(Class.name);
console.assert(
instances.every(instance => instance.clicks === samples),
`expected ${length} clicks, got ${instances[0].clicks} instead`
);
currentTarget.parentNode.removeChild(currentTarget);
res();
});
});
};
// Helpers
const button = textContent => {
const el = document.createElement('button');
el.textContent = textContent;
return document.body.appendChild(el);
};
I guess instead of "expected ${length} clicks, got ${instances[0].clicks} instead
" we should use "expected ${samples} clicks, got ${instances[0].clicks} instead
", aren't we?
Just one more thing.
I launched a code of this benchmark (Chrome Version 64.0.3282.186 (Official Build) (64-bit), MacOS High Sierra) and got some different results, which shows an enormous memory consumption for handleEvent
.
@WebReflection, can you, please, comment on that?
I've posted a question on StackOverflow.
The answer is pretty simple.
handleEvent consumed that much memory due to issue in the benchmark.
The thing is that strings concatenation ('on' + e.type
) causes new string allocation in heap on each iteration.
If we just replace this['on' + e.type]
to switch(e.type) {case 'click': this.onclick(e);}
, then we get results, which are reflecting initial idea regarding benefits of using handleEvent.