起因

最近在做 hessian 序列化的性能优化,偶然发现 ES6 Class 的继承写法性能不太好。我本来想通过覆盖 hessian.EncoderV2 的某些方法来做一些优化,如下:

  1. const LRU = require('ylru');
  2. const lru = new LRU(1000);
  3. class Encoder extends hessian.EncoderV2 {
  4. writeString(str) {
  5. let buf = lru.get(str);
  6. if (buf) {
  7. this.byteBuffer.fastPut(buf);
  8. return this;
  9. }
  10. const start = this.byteBuffer.position();
  11. super.writeString(str);
  12. buf = this.byteBuffer.fastCopy(start);
  13. lru.set(str, buf);
  14. return this;
  15. }
  16. }

但是让我意外的是,优化以后性能反而下降了,于是我尝试将覆盖的代码都注释掉,只保留继承的结构

  1. class Encoder extends hessian.EncoderV2 {}

结果性能仍然是下降的,所以我将矛头指向了 ES6 Classextends 写法上。

证明

为了更加清楚的证明上面的猜想,我写了一个 benchmark。对比下面几种类定义方式使用的性能

  • 通过 ES6 Class 定义一个类(无继承)

  • 通过构造函数 + prototype 方式定义类

  • 通过 ES6 Class + extends 定义一个类

  • 通过 util.inherits 方式继承一个类

  • 通过 ES6 Class + extends 嵌套继承类(两层)

  • 通过 util.inherits 嵌套继承(两层)

结果:

node v8.9.4 ES6 Class Extends 性能分析 - 图1 node v9.4.0 ES6 Class Extends 性能分析 - 图2 从 benchmark 结果看 ES6 Class + extends 的性能相比其他确实要慢不少,另外在测试的过程中,我发现 extends 继承的层次对于性能也有比较大的影响:

一层继承

  1. class A {
  2. calculate() {
  3. return 1000000 * 1000000;
  4. }
  5. }
  6. class B extends A {}

ES6 Class Extends 性能分析 - 图3

两层继承

  1. class A {
  2. calculate() {
  3. return 1000000 * 1000000;
  4. }
  5. }
  6. class B extends A {}
  7. class C extends B {}

ES6 Class Extends 性能分析 - 图4

三层继承

  1. class A {
  2. calculate() {
  3. return 1000000 * 1000000;
  4. }
  5. }
  6. class B extends A {}
  7. class C extends B {}
  8. class D extends C {}

ES6 Class Extends 性能分析 - 图5

结论

  • ES6 Class + extends 的性能确实不算好

  • 继承的深度越深,性能越差,基本上是多一层慢一倍,继承链上所有类(除最顶端,比如:上面例子中的 A)性能都会受影响

  • util.inherits 方式表现稳定,受继承层次影响很小

观点

  • 写这篇文章的目的不是让你不要用 ES6 Class + extends,只是客观反映目前 js 引擎对于各种类定义、继承方式的处理现状。对于普通的场景我建议你该怎么写还怎么写,因为我相信 v8 和其他 js 引擎迟早会解决这个问题,没有必要因为一点性能牺牲代码的优雅。

  • 虽然 ES6 Class + extends从上面 benchmark 的数据不如直接原型链方式高效,但也是千万级 ops/sec 所以基本上不太会成为你代码的瓶颈。

  • 对于一些底层模块或者性能要求非常高的场景,可以考虑减少继承的深度和优先使用 util.inherits方式。