起因
最近在做 hessian 序列化的性能优化,偶然发现 ES6 Class
的继承写法性能不太好。我本来想通过覆盖 hessian.EncoderV2 的某些方法来做一些优化,如下:
const LRU = require('ylru');
const lru = new LRU(1000);
class Encoder extends hessian.EncoderV2 {
writeString(str) {
let buf = lru.get(str);
if (buf) {
this.byteBuffer.fastPut(buf);
return this;
}
const start = this.byteBuffer.position();
super.writeString(str);
buf = this.byteBuffer.fastCopy(start);
lru.set(str, buf);
return this;
}
}
但是让我意外的是,优化以后性能反而下降了,于是我尝试将覆盖的代码都注释掉,只保留继承的结构
class Encoder extends hessian.EncoderV2 {}
结果性能仍然是下降的,所以我将矛头指向了 ES6 Class
和 extends
写法上。
证明
为了更加清楚的证明上面的猜想,我写了一个 benchmark。对比下面几种类定义方式使用的性能
通过
ES6 Class
定义一个类(无继承)通过构造函数 +
prototype
方式定义类通过
ES6 Class + extends
定义一个类通过
util.inherits
方式继承一个类通过
ES6 Class
+extends
嵌套继承类(两层)通过
util.inherits
嵌套继承(两层)
结果:
node v8.9.4
node v9.4.0
从 benchmark 结果看 ES6 Class
+ extends
的性能相比其他确实要慢不少,另外在测试的过程中,我发现 extends
继承的层次对于性能也有比较大的影响:
class A {
calculate() {
return 1000000 * 1000000;
}
}
class B extends A {}
class A {
calculate() {
return 1000000 * 1000000;
}
}
class B extends A {}
class C extends B {}
class A {
calculate() {
return 1000000 * 1000000;
}
}
class B extends A {}
class C extends B {}
class D extends C {}
结论
ES6 Class
+extends
的性能确实不算好继承的深度越深,性能越差,基本上是多一层慢一倍,继承链上所有类(除最顶端,比如:上面例子中的 A)性能都会受影响
util.inherits
方式表现稳定,受继承层次影响很小
观点
写这篇文章的目的不是让你不要用 ES6 Class + extends,只是客观反映目前 js 引擎对于各种类定义、继承方式的处理现状。对于普通的场景我建议你该怎么写还怎么写,因为我相信 v8 和其他 js 引擎迟早会解决这个问题,没有必要因为一点性能牺牲代码的优雅。
虽然 ES6 Class + extends从上面 benchmark 的数据不如直接原型链方式高效,但也是千万级 ops/sec 所以基本上不太会成为你代码的瓶颈。
对于一些底层模块或者性能要求非常高的场景,可以考虑减少继承的深度和优先使用 util.inherits方式。