While benchmarking code in my library I came across very interesting and disappointing finding - it looks like invocation of class method is 20-40 times slower than of the same standalone function.
Shortly, for me it means - "for better performance - do not implement very-frequently called functionality via JS classes". Accessing library functions via "namespace object" also has performance penalty. That problem may affect approach to design a library.
I feel I'm missing something. Please let me know if there is something wrong in perf-tests below.
Here is summary of results, windows + intel 9600k (nodejs 14, Chrome 86, FF82)
ops/s in: node14 slow Ch86 slow FF82 slow
function(a, b) 4195 3934 1625
namespace.method(a, b) 210 x20 2702 x1.5 1305 x1.24
instance.method(a, b) 126 x33 162 x24 89 x18
prototype.method(a, b) 105 x40 154 x25 57 x28
class.method(a, b) 110 x38 153 x25 57 x28
Test script for https://benchmarkjs.com/, portions where also copy/pasted to https://jsbench.me/ for in-browser testing.
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite("function-vs-method");
const nCycles = 1000000;
suite
.add('function(a, b)', function () {
function method(a, b) {
return a + b;
}
for( let i = 0; i < nCycles; i++ ) {
method(i, i)
}
})
.add('namespace.method(a, b)', function () {
function method(a, b) {
return a + b;
}
let namespace = {}
namespace.method = method
for( let i = 0; i < nCycles; i++ ) {
namespace.method(i, i)
}
})
.add('instance.method(a, b)', function () {
function method(a, b) {
return a + b;
}
function Test() {}
let instance = new Test()
instance.method = method
for( let i = 0; i < nCycles; i++ ) {
instance.method(i, i)
}
})
.add('prototype.method(a, b)', function () {
Test.prototype.method = (a, b) => {
return a + b;
}
function Test() {
}
let proto = new Test()
for( let i = 0; i < nCycles; i++ ) {
proto.method(i, i)
}
})
.add('class.method(a, b)', function () {
class Test {
method(a, b) {
return a + b;
}
}
let c = new Test()
for( let i = 0; i < nCycles; i++ ) {
c.method(i, i)
}
})
// add listeners
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run();
You are including the initialization in your tests instead of just comparing the method/function call.
Example: You are comparing "declaring and calling a function" vs "declaring a class, instantiating a class object and calling a class mehtod".
If you extract the setups outside of the actual tests, the results look quite different.
Additionally, each run of your code with nCycles and the for loop yielded quite different results. I changed it to one invocation and now see constant results across test runs.
Disclaimer: I haven't done much benchmarking so far but wanted to try my best at an answer. @everyone please feel free to explain why I might be on the wrong track. @op please don't blindly trust this answer.
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite('function-vs-method');
function method (a, b) {
return a + b;
}
let namespace = {};
namespace.method = function (a, b) {
return a + b;
};
function Test () {
}
let instance = new Test();
instance.method = function (a, b) {
return a + b;
};
TestProto.prototype.method = function (a, b) {
return a + b;
};
function TestProto () {
}
let proto = new TestProto();
class TestClass {
method (a, b) {
return a + b;
}
}
let c = new TestClass();
suite
.add('function(a, b)', function () {
method(1, 2);
})
.add('namespace.method(a, b)', function () {
namespace.method(1, 2);
})
.add('instance.method(a, b)', function () {
instance.method(1, 2);
})
.add('prototype.method(a, b)', function () {
proto.method(1, 2);
})
.add('class.method(a, b)', function () {
c.method(1, 2);
})
// add listeners
.on('cycle', function (event) {
console.log(String(event.target));
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run();
Log:
function(a, b) x 1,395,900,922 ops/sec ±0.11% (93 runs sampled)
namespace.method(a, b) x 1,397,715,256 ops/sec ±0.06% (98 runs sampled)
instance.method(a, b) x 1,397,494,031 ops/sec ±0.08% (96 runs sampled)
prototype.method(a, b) x 1,395,546,437 ops/sec ±0.09% (98 runs sampled)
class.method(a, b) x 1,398,311,922 ops/sec ±0.07% (96 runs sampled)
Fastest is class.method(a, b),namespace.method(a, b),instance.method(a, b),function(a, b)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With