[英]Can Java compiler optimize adding to a set in recursive methods
出于好奇,主要问一个简单的问题,即什么Java编译器足够聪明。 我知道不是所有编译器的构建方式都一样,但是我想知道其他人是否认为期望对我可能会遇到的大多数编译器进行优化是合理的,而不是在特定版本或所有版本上都可以进行优化。
因此,可以说我有一些树形结构,我想收集节点的所有后代。 有两种简单的方法可以递归执行此操作。
对我来说,更自然的方法是这样的:
public Set<Node> getDescendants(){
Set<Node> descendants=new HashSet<Node>();
descendants.addall(getChildren());
for(Node child: getChildren()){
descendants.addall(child.getDescendants());
}
return descendants;
}
但是,假设没有编译器优化和一棵像样的树,这可能会变得相当昂贵。 在每个递归调用中,我创建并完全填充一个集合,仅返回该集合的堆栈,以便调用方法可以将我的返回集合的内容添加到其后代集合的版本中,丢弃刚刚构建并填充到其中的版本递归调用。
因此,现在我创建了许多集合,只是希望一旦我返回它们的内容就将其丢弃。 我不仅为构建集合付出了很小的初始化成本,而且还为将一个集合的所有内容移入更大的集合中付出了更大的代价。 在大树中,我大部分时间都花在将节点从集合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认识到我只是在创建临时集,以便返回到调用方法而避免了在集之间进行浪费的复制?
编辑:清理坏的复制粘贴作业,这导致我的示例方法两次添加所有内容。 当您的“优化”代码比常规代码慢时,您就会知道有些不好。
问题是,如果我担心优化这种方法,是否需要做第二种选择?
绝对可以。 如果性能是一个问题(大多数时候不是!),那么您就需要它。
编译器进行了很多优化,但是规模不同。 基本上,它仅使用一种方法,并且会优化其中的最常用路径。由于内联量很大,因此可以跨方法调用进行某种优化,但与上面的方法不同。
它也可以优化不必要的分配,但仅限于非常简单的情况。 也许像
int sum(int... a) {
int result = 0;
for (int x : a) result += x;
return result;
}
调用sum(1, 2, 3)
意味着为varargs参数分配int[3]
,这可以消除(如果编译器确实这样做,则是另一个问题)。 它甚至可以发现结果是一个常数(我怀疑确实如此)。 如果结果没有被使用,它可以执行无效代码消除(这种情况经常发生)。
您的示例涉及分配整个HashMap
及其所有条目,并且要复杂几个数量级。 编译器不知道HashMap
工作方式,也无法确定m.addAll(m1)
的set m
包含m1
所有成员。 没门。
这是算法优化,而不是底层优化。 那就是人类仍然需要的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.