给定接口(非常大并且由语言定义生成):
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) {...}
}
现在编译接口VisitorA(包含大约2.000个重载方法)需要大约10秒。 编译接口VisitorB和VisitorC需要大约1.5分钟。 但是当我们尝试编译接口VisitorD时,Java 8编译器大约需要7分钟!
我们已经尝试过,以下解决方案有所帮助:
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) {...}
}
现在访客D的编制时间只需要2分钟左右。 但这仍然很多。
extends VisitorBPlain, VisitorCPlain
,那么此接口的编译时间大约需要15秒 - 即使它有大约5.000个默认方法。但是我们需要VisitorD与VisitorB和VisitorC兼容(通过直接扩展或与中间Plain接口的间接扩展)以用于构建原因。我也阅读了类似问题的答案: slow JDK8 compilation 但问题似乎是通用类型推断: "在基于通用目标类型的重载解决方案中,Java 8中存在严重的性能回归。"
所以这有点不同,如果有人会有小费或好消息 解释为什么会这样;我会非常感激。
谢谢你, 迈克尔
答案 0 :(得分:2)
这个答案归功于@Brian Goetz。
我创建了一个虚拟测试,一旦所有visit
方法被覆盖和重载,在visitX
方法得到不同名称的另一个时间。
结果比我想象的更令人惊讶:
当重载和覆盖visit
方法时,编译器需要 30分钟!
当我在一个访问者类中唯一地重命名visit
方法时,编译器只需 46秒。
以下是虚拟测试的源代码: https://drive.google.com/open?id=0B6L6K365bELNUkVYMHZnZ0dGREk
以下是我电脑编译时的截图:
VisitorN
包含重载和覆盖的visit
方法。
VisitorG
包含优化的visitX
方法,这些方法只会被覆盖但不会再过载。
使用" plain"使用不同visitX
方法的方法,然后编译Visitor_S
和VisitorPlain_S
仅需要 22秒(比直接重载{{1}的方法快两倍}} 方法)。
default visitX
有Visitor_S
个方法,但它扩展default
没有VisitorPlain_S
方法。 default
扩展了其他" plain"没有VisitorPlain_S
方法的访问者。
但我仍然不明白 - 仅仅因为我的理论兴趣,是桥接方法的事实: 在https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html桥接方法中,只发生类型擦除,但在示例中我们没有泛型,因此类型擦除应该根本不起作用。 - 也许任何人都有一个很好的解释为什么它仍然存在。
答案 1 :(得分:1)
在针对此问题的额外会议之后,我们发现了第一个答案的以下限制:
第一个答案非常适合“静态”访问者,因为它们在ANTLR中使用,因为您没有语言界面,因此visit
方法确切地知道孩子ASTTypes
。在MontiCore中,我们可以定义一个接口语法元素,现在将在这里解释:
grammar MontiArc {
MontiArc = "component" Name "{" ArcElement* "}";
interface ArcElement;
Port implements ArcElement = "port" ... ;
}
grammar MontiArcAutomaton extends MontiArc {
Automaton implements ArcElement = State | Transition;
State = "state" ... ;
Transition = ... "->" ...;
}
MontiArcAST
的访问者并不确切知道应该调用哪个accept
方法,因为您不知道是应该调用PortAST#accept
还是调用未知方法{{1}由于语法扩展,将在稍后介绍。这就是为什么我们使用“双重调度”,但因此State#accept
方法必须具有相同的名称(因为当我们为{{生成访问者时,我们无法知道不存在的方法visit
语法。
我们考虑过生成visitState(StateAST node)
方法,并使用大MontiArc
- if-cascade从一般visitX
方法委托此方法。但是这需要在部署我的jar语法文件visit
之后向instanceof
添加额外的if
语句,这会破坏我们的模态。
我们会尝试进一步分析问题,如果我们找到了一个新的方法来生成大型动态访问者,我会及时向您提供最新信息。
答案 2 :(得分:0)
我们想出了如何为我们解决问题: 因为重载的继承方法有生成器,我们在生成器中有一个错误 与继承自的方法体相同的方法体。
这对我们来说意味着我们有两种方法可以解决它:
有趣的是,(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);
}
}
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);
}
}
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);
}
}
版本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);
}
}
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);
}
}
版本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);
}
}
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);
}
}
版本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);
}
}
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);
}
}
这里我只在不同的版本中展示了DelegatorVisitorA.java,DelegatorVisitorB.java和DelegatorVisitorC.java。 其他委托访问者DelegatorVisitorD.java到DelegatorVisitorI.java遵循相同的模式。 (DelegatorVisitorI属于扩展语言H的语言I.语言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
所有四个不同版本的Java文件具有相同的行为, 但是由于重复的代码,编译过程需要更长的时间。
同样有趣的是,如果你复制方法并且不使用任何继承,那么编译是最快的,即使文件在很长的继承链之后变得更大。
(我个人无法理解版本2和版本3之间的大时差,也许这是javac编译器分析过程中的一个错误。)