考虑到接口(这些接口非常大,由语言定义生成):
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个重载方法)需要大约10s。编译接口VisitorB和VisitorC分别需要大约1.5分钟。但是当我们试图编译接口VisitorD时,Java8编译器需要大约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) {...}
}和现在visitorD的编译时间只需要大约2分钟。,但这仍然是很多。
extends VisitorBPlain, VisitorCPlain的两个扩展关系,那么这个接口的编译时间需要15s --尽管它有大约5.000个默认方法。但出于浇铸的原因,我们需要VisitorD与VisitorB和VisitorC兼容(无论是直接扩展还是间接扩展,也可以是中间平面接口)。我还阅读了类似问题的答案:slow JDK8 compilation,但问题似乎是泛型类型推断:“在Java8中,在基于泛型目标类型的重载解析方面存在严重的性能倒退。”
所以这是不一样的,如果任何人有一个提示或一个很好的解释为什么是这样的,我会非常感谢。
谢谢你,迈克尔
发布于 2017-03-09 19:47:01
我们想出了如何解决这个问题的方法:我们在生成器中有一个错误,因为重载继承方法与继承的方法体相同。
这对我们来说意味着我们有两种方法来解决这个问题:
有趣的是,(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编译器分析过程中的一个错误。)
发布于 2016-08-25 22:50:34
这个答案的功劳是“Brian”。
我创建了一个虚拟测试,一旦所有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桥中,方法只发生类型擦除,但是在示例中,我们没有泛型,所以类型擦除应该完全不起作用。-也许任何人都能很好地解释为什么它仍然重要。
发布于 2016-08-26 11:00:17
在就这个问题举行了一次额外的会议之后,我们发现了第一个答案的以下局限性:
第一个答案对于“静态”访问者非常有用,因为他们在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,甚至不知道方法State#accept,因为语法扩展将在稍后引入该方法。这就是为什么我们使用“双重分派”,但因此visit方法必须具有相同的名称(因为我们无法知道方法visitState(StateAST node),当我们为MontiArc语法生成访问者时,这个方法不存在。
我们考虑生成visitX方法,并使用大型instanceof-if级联从一般的instanceof方法委托给该方法。但这将需要在部署语法if的jar文件之后向visit(MontiArcAST node)添加额外的MontiArc语句,这将破坏我们的调制解调器。
我们将尝试进一步分析这个问题,如果我们找到了一种新的方法,如何产生大的动态访问者,我将让您了解最新的情况。
https://stackoverflow.com/questions/39111439
复制相似问题