Java8 对具有数千个同名默认方法的接口(interface)进行缓慢编译

标签 java interface java-8 compile-time default-method

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

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) {...}
}

现在编译接口(interface) VisitorA(包含大约 2.000 个重载方法)需要大约 10 秒。 编译接口(interface) VisitorB 和 VisitorC 各需要大约 1.5 分钟。 但是当我们尝试编译接口(interface) 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) {...}
}

而现在visitorD的编译时间只需要2分钟左右。 但这仍然很多。

  • 有人知道如何将 VisitorD 的编译时间缩短到几秒钟吗?
  • 如果我们去掉 VisitorD、extends VisitorBPlain、VisitorCPlain 的两个扩展关系,那么这个接口(interface)的编译时间大约需要 15 秒——尽管它有大约 5.000 个默认方法。但是出于转换原因,我们需要 VisitorD 与 VisitorB 和 VisitorC 兼容(通过直接扩展或具有中间纯接口(interface)的间接扩展)。

我也看了类似问题的答案: slow JDK8 compilation 但问题似乎出在泛型类型推断上: “当涉及到基于通用目标类型的重载解析时,Java 8 中存在严重的性能退化。”

所以这有点不同,如果有人有小费或好的 解释为什么会这样;我将不胜感激。

谢谢, 迈克尔

最佳答案

这个答案归功于@Brian Goetz。

我创建了一个虚拟测试,一旦所有 visit 方法都被覆盖和重载,而在另一时间 visitX 方法有不同的名称。

结果比我想象的更惊人: 在重载覆盖visit方法时,编译器用了将近30分钟! 当我在一个访问者类中唯一重命名 visit 方法时,编译器只需要 46 秒

这是虚拟测试的源代码: https://drive.google.com/open?id=0B6L6K365bELNUkVYMHZnZ0dGREk

下面是我电脑上编译时间的截图: VisitorN 包含重载和覆盖的 visit 方法。 VisitorG 包含优化的 visitX 方法,这些方法只是被覆盖但不再重载。 <code>VisitorN</code> contains overloaded and overwritten <code>visit</code> methods <code>VisitorG</code> contains the optimized <code>visitX</code> methods, which are only overwritten but not overloaded anymore

使用具有不同visitX 方法的“普通”方法,然后编译Visitor_SVisitorPlain_S 只需要大约22 秒(比直接重载 default visitX 方法的方法快两倍)。 Visitor_S 具有default 方法,但它扩展了没有default 方法的VisitorPlain_SVisitorPlain_S 扩展了其他没有default 方法的“普通”访问者。 <code>Visitor_S</code> has <code>default</code> methods, but it extends <code>VisitorPlain_S</code> having no <code>default</code> methods. <code>VisitorPlain_S</code> extends other "plain" visitors without <code>default</code> methods.

但我仍然不明白——只是出于我的理论兴趣,桥接方法的事实是: 在 https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html桥接方法只在类型删除时出现,但在示例中我们没有泛型,因此类型删除根本不应该起作用。 - 也许任何人都能很好地解释为什么它仍然很重要。

关于Java8 对具有数千个同名默认方法的接口(interface)进行缓慢编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39111439/

相关文章:

macos - 如何在 Mac 上监控 en0 网络接口(interface),而无需使用 sudo?

java - 大数字反序列化抛出 NumberFormatException

java - Gson:使用不带注释的方式排除特定类上的字段 - 2

delphi - 在Delphi中实现接口(interface)时,实现方法不在public部分有关系吗?

java - 读取重复事件时出现问题

c++ - 为什么示例代码访问 IUnknown 中已删除的内存?

java - 使用 Java lambda 对 SQL 中的对象进行分组和求和?

java - DoubleStream 和 LongStream 的范围方法

java - 如何在 Java 中获取任何操作系统(如果有)的 "Favorites"文件夹的内容?

java - Android 致命信号 11 (SIGSEGV) 轮盘教程