【发布时间】:2014-06-02 10:25:14
【问题描述】:
简单的问题主要是出于对 Java 编译器足够聪明可以做什么的好奇。我知道并非所有编译器都是平等构建的,但我想知道其他人是否认为期望对我可能运行的大多数编译器进行优化是合理的,而不是它是否适用于特定版本或所有版本。
假设我有一些树结构,我想收集一个节点的所有后代。有两种简单的递归方法。
对我来说,更自然的方法是这样的:
public Set<Node> getDescendants(){
Set<Node> descendants=new HashSet<Node>();
descendants.addall(getChildren());
for(Node child: getChildren()){
descendants.addall(child.getDescendants());
}
return descendants;
}
但是,假设没有编译器优化和大小合适的树,这可能会变得相当昂贵。在每次递归调用中,我创建并完全填充一个集合,只返回那个设置堆栈,以便调用方法可以将我返回集合的内容添加到后代集合的 it's 版本中,丢弃该版本刚刚在递归调用中构建和填充。
所以现在我创建了许多集合,只是为了让它们在我返回它们的内容后立即被丢弃。我不仅为构建集合支付了少量的初始化成本,而且还为将一个集合的所有内容移动到更大的集合中付出了更大的成本。在大树中,我的大部分时间都花在将内存中的节点从集合 A 移动到 B 上。我认为这甚至使我的算法 O(n^2) 而不是 O(n),因为复制节点所花费的时间;不过如果我开始计算的话,结果可能是 O(N log(n))。
我可以使用一个简单的 getDescendants 方法来调用如下所示的辅助方法:
public Set<Node> getDescendants(){
Set<node> descendants=new HashSet<Node>();
getDescendantsHelper(descendants);
return descendants;
}
public Set<Node> getDescendantsHelper(Set<Node> descendants){
descendants.addall(getChildren());
for(Node child: getChildren()){
child.getDescendantsHelper(descendant);
}
return nodes;
}
这样可以确保我只创建一个集合,而不必浪费时间从一个集合复制到另一个集合。但是,它需要编写两个方法而不是一个,并且通常感觉比较麻烦。
问题是,如果我担心优化这种方法,是否需要执行选项二?或者我是否可以合理地期望 java 编译器或 JIT 认识到我只是为了方便返回调用方法而创建临时集合并避免集合之间的浪费复制?
编辑:清理了导致我的示例方法将所有内容添加两次的不良复制粘贴作业。当您的“优化”代码比您的常规代码慢时,您就知道有些事情是不好的。
【问题讨论】:
-
我会用第二种方法,不用担心它看起来很麻烦。根据我的经验,让编译器弄清楚如何优化第一个编译器是方式太多了。它似乎需要接近人类的推理能力。也许技术已经足够先进,可以做这样的事情,但我对此表示怀疑。
-
我从未见过可以做到这一点的编译器,Java 编译器在字节码优化方面做得很少。自己编码。
-
相关阅读:stackoverflow.com/questions/3616483/…(顺便说一句,Scala 可以进行尾递归——有时通过将递归方法编译成循环;它在 JVM 中运行。)
标签: java optimization recursion compiler-construction jit