给定接口(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
方法,这些方法只是被覆盖但不再重载。
使用具有不同visitX
方法的“普通”方法,然后编译Visitor_S
和VisitorPlain_S
只需要大约22 秒(比直接重载 default visitX
方法的方法快两倍)。
Visitor_S
具有default
方法,但它扩展了没有default
方法的VisitorPlain_S
。 VisitorPlain_S
扩展了其他没有default
方法的“普通”访问者。
但我仍然不明白——只是出于我的理论兴趣,桥接方法的事实是: 在 https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html桥接方法只在类型删除时出现,但在示例中我们没有泛型,因此类型删除根本不应该起作用。 - 也许任何人都能很好地解释为什么它仍然很重要。
关于Java8 对具有数千个同名默认方法的接口(interface)进行缓慢编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39111439/