Java8对具有相同名称的数千个默认方法的接口进行了慢速编译

Mic*_*ern 7 java interface compile-time java-8 default-method

给定接口(非常大并且由语言定义生成):

interface VisitorA {
   default void visit(ASTA1 node) {...}
   ...
   default void visit(ASTA2000 node) {...}
}

interface VisitorB extends VisitorA {
   default void visit(ASTB1 node) {...}
   ...
   default void visit(ASTB1000 node) {...}

   // due to language embedding all visit methods of VisitorA
   // must be overwritten
   @Override
   default void visit(ASTA1 node) {...}
   ...
   @Override
   default void visit(ASTA2000 node) {...}
}

interface VisitorC extends VisitorA {
   default void visit(ASTC1 node) {...}
   ...
   default void visit(ASTC1000 node) {...}

   // due to language embedding all visit methods of VisitorA
   // must be overwritten
   @Override
   default void visit(ASTA1 node) {...}
   ...
   @Override
   default void visit(ASTA2000 node) {...}
}

interface VisitorD extends VisitorB, VisitorC {
   default void visit(ASTD1 node) {...}
   ...
   default void visit(ASTD1000 node) {...}

   // due to language embedding all visit methods of VisitorA,
   // VisitorB, and VisitorC must be overwritten
   @Override
   default void visit(ASTA1 node) {...}
   ...
   @Override
   default void visit(ASTA2000 node) {...}

   @Override
   default void visit(ASTB1 node) {...}
   ...
   @Override
   default void visit(ASTB1000 node) {...}

   @Override
   default void visit(ASTC1 node) {...}
   ...
   @Override
   default void visit(ASTC1000 node) {...}
}
Run Code Online (Sandbox Code Playgroud)

现在编译接口VisitorA(包含大约2.000个重载方法)需要大约10秒.编译接口VisitorB和VisitorC需要大约1.5分钟. 但是当我们尝试编译接口VisitorD时,Java 8编译器大约需要7分钟!

  • 有没有人知道为什么需要这么多时间来编译VisitorD?
  • 是因为默认方法的继承吗?
  • 或者是因为钻石星座,VisitorB以及VisitorC扩展了VisitorA和VisitorD再次延伸VisitorB和VisitorC?

我们已经尝试过,以下解决方案有所帮助:

 interface VisitorAPlain {
   void visit(ASTA1 node);
   ...
   void visit(ASTA2000 node);
}

interface VisitorA extends VisitorAPlain {
   ... // has same default methods as VisitorA above
}

interface VisitorBPlain extends VisitorAPlain {
   void visit(ASTB1 node);
   ...
   void visit(ASTB1000 node);
}

interface VisitorB extends VisitorBPlain {
   ... // has same default methods as VisitorB above
}

interface VisitorCPlain extends VisitorAPlain {
   void visit(ASTC1 node);
   ...
   void visit(ASTC1000 node);
}

interface VisitorC extends VisitorCPlain {
   ... // has same default methods as VisitorC above
}

interface VisitorD extends VisitorBPlain, VisitorCPlain {
   default void visit(ASTD1 node) {...}
   ...
   default void visit(ASTD1000 node) {...}

   // due to language embedding all visit methods of VisitorAPlain,
   // VisitorBPlain, and VisitorCPlain must be overwritten
   @Override
   default void visit(ASTA1 node) {...}
   ...
   default void visit(ASTA2000 node) {...}

   @Override
   default void visit(ASTB1 node) {...}
   ...
   default void visit(ASTB1000 node) {...}

   @Override
   default void visit(ASTC1 node) {...}
   ...
   default void visit(ASTC1000 node) {...}
}
Run Code Online (Sandbox Code Playgroud)

而现在访客D的编译时间只需要2分钟左右. 但这仍然是很多.

  • 有没有人知道如何将VisitorD的编译时间减少到几秒钟?
  • 如果我们删除了VisitorD的两个扩展关系extends VisitorBPlain, VisitorCPlain,那么这个接口的编译时间大约需要15秒 - 即使它有大约5.000个默认方法.但是我们需要VisitorD与VisitorB和VisitorC兼容(通过直接扩展或与中间Plain接口的间接扩展)以用于转换原因.

我还阅读了类似问题的答案: 缓慢的JDK8编译, 但问题似乎是泛型类型推断:"当涉及基于通用目标类型的重载解析时,Java 8中存在严重的性能回归."

所以这有点不同,如果有人会有一个提示或一个很好的解释为什么会这样; 我会非常感激.

谢谢你,迈克尔

Mic*_*ern 0

我们找到了解决问题的方法:生成器中存在一个错误,因为重载的继承方法与继承的方法具有相同的方法体。

这对我们来说意味着我们有两种方法来解决它:

  • (a) 不再生成我们继承的方法
  • (b) 生成所有方法,但删除接口继承

有趣的是(a)比(b)需要更多的编译时间。

我在 Mac 上做了一个实验来展示我们在修复过程中发现的结果,您可以在以下位置下载该结果: https://drive.google.com/open ?id=0B6L6K365bELNWDRoeTF4RXJsaFk

我这里只是描述一下实验的基本文件,以及结果。也许有人觉得它有用。

版本 1 是 (b),如下所示:

DelegatorVisitorA.java

interface DelegatorVisitorA extends VisitorA {
  VisitorA getVisitorA();  

  default void visit(AST_A1 node) {
    getVisitorA().visit(node);
  }
  ...
  default void visit(AST_A49 node) {
    getVisitorA().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

DelegatorVisitorB.java

interface DelegatorVisitorB extends VisitorB {
  VisitorA getVisitorA();  
  default void visit(AST_A1 node) {
    getVisitorA().visit(node);
  }
  ...
  default void visit(AST_A49 node) {
    getVisitorA().visit(node);
  }
  VisitorB getVisitorB();  

  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

DelegatorVisitorC.java

interface DelegatorVisitorC extends VisitorC {
  VisitorA getVisitorA();
  default void visit(AST_A1 node) {
    getVisitorA().visit(node);
  }
  ...
  default void visit(AST_A49 node) {
    getVisitorA().visit(node);
  }
  VisitorB getVisitorB();  
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
  VisitorC getVisitorC();  
  default void visit(AST_C1 node) {
    getVisitorC().visit(node);
  }
  ...
  default void visit(AST_C49 node) {
    getVisitorC().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

版本 2 是 (a),如下所示:

DelegatorVisitorA.java 与版本 1 中相同

DelegatorVisitorB.java

interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
  VisitorB getVisitorB();
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

DelegatorVisitorC.java

interface DelegatorVisitorC extends VisitorC , DelegatorVisitorB{
  VisitorB getVisitorB();
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

版本 3(我们的中间步骤,但也是错误的)如下所示:

DelegatorVisitorA.java 与版本 1 中相同

DelegatorVisitorB.java

interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
  VisitorB getVisitorB();
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

DelegatorVisitorC.java

interface DelegatorVisitorC extends VisitorC , DelegatorVisitorA, DelegatorVisitorB{
  VisitorB getVisitorB();
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

版本 4(导致这篇文章的旧版本)如下所示:

DelegatorVisitorA.java 与版本 1 中相同

DelegatorVisitorB.java

interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
  VisitorA getVisitorA();  
  default void visit(AST_A1 node) {
    getVisitorA().visit(node);
  }
  ...
  default void visit(AST_A49 node) {
    getVisitorA().visit(node);
  }
  VisitorB getVisitorB();  

  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

DelegatorVisitorC.java

interface DelegatorVisitorC extends VisitorB , DelegatorVisitorA, DelegatorVisitorB{
  VisitorA getVisitorA();
  default void visit(AST_A1 node) {
    getVisitorA().visit(node);
  }
  ...
  default void visit(AST_A49 node) {
    getVisitorA().visit(node);
  }
  VisitorB getVisitorB();  
  default void visit(AST_B1 node) {
    getVisitorB().visit(node);
  }
  ...
  default void visit(AST_B49 node) {
    getVisitorB().visit(node);
  }
  VisitorC getVisitorC();  
  default void visit(AST_C1 node) {
    getVisitorC().visit(node);
  }
  ...
  default void visit(AST_C49 node) {
    getVisitorC().visit(node);
  }
}
Run Code Online (Sandbox Code Playgroud)

这里我只展示了不同版本的DelegatorVisitorA.java、DelegatorVisitorB.java和DelegatorVisitorC.java。其他委托访问者 DelegatorVisitorD.java 到 DelegatorVisitorI.java 遵循相同的模式。(DelegatorVisitorI 属于语言 I,它扩展了语言 H。语言 H 有 DelegatorVisitorH,语言 H 扩展了语言 G,依此类推。)

上述四个不同版本生成的 DelegatorVisitorI.java 的编译结果需要如此多的时间:

结果是:

Version 1:
103-240:srcV1 michael$ time javac DelegatorVisitorI.java

real    0m1.859s
user    0m5.023s
sys 0m0.175s



Version 2:
103-240:srcV2 michael$ time javac DelegatorVisitorI.java

real    0m3.364s
user    0m7.713s
sys 0m0.342s



Version 3:
103-240:srcV3 michael$ time javac DelegatorVisitorI.java

real    2m58.009s
user    2m56.787s
sys 0m1.718s



Version 4:
103-240:srcV4 michael$ time javac DelegatorVisitorI.java

real    14m14.923s
user    14m3.738s
sys 0m5.141s
Run Code Online (Sandbox Code Playgroud)

所有四个不同版本的 Java 文件具有相同的行为,但由于重复的代码,编译过程需要更长的时间。

另外有趣的是,如果复制方法并且不使用任何继承,则编译速度是最快的,即使文件在经过很长的继承链后也会变得更大。

(版本2和版本3之间的巨大时间差异我个人无法理解,也许这是javac编译器分析过程中的一个错误。)