【发布时间】:2020-01-09 05:00:36
【问题描述】:
我有一个简单的递归函数,可以构造一定深度的二叉树。
我认为带有 DFS 堆栈的迭代版本会达到类似的性能,但速度却惊人地慢了 3 倍!
更准确地说,在我的机器上,深度为 15 的递归版本大约需要 330_000 ns,而带有堆栈的迭代版本需要大约 950_000 ns。
能否将令人惊讶的性能归因于优越的缓存局部性(这对于递归函数来说显然更好)。
我用于性能基准测试的代码:
class Main {
public static void main(String[] args) {
long startTime = System.nanoTime();
long runs;
Tree t = null;
for(runs=0; (System.nanoTime() - startTime)< 3_000_000_000L ; runs++) {
t = createTree3(15);
}
System.out.println((System.nanoTime() - startTime) / runs + " ns/call");
}
static Tree createTree(int depth) {
Tree t = new Tree();
createTreeHlp(t, depth);
return t;
}
static void createTreeHlp(Tree tree, int depth) {
if (depth == 0)
tree.init(0, null, null);
else {
tree.init(depth, new Tree(), new Tree());
createTreeHlp(tree.leftChild, depth -1);
createTreeHlp(tree.rghtChild, depth -1);
}
}
static Tree createTree3(int depth_) {
TreeStack stack = new TreeStack();
Tree result = new Tree();
stack.put(result, depth_);
while (!stack.isEmpty()) {
int depth = stack.depth[stack.stack][stack.index];
Tree tree = stack.tree[stack.stack][stack.index];
stack.dec();
if (depth == 0)
tree.init(0, null, null);
else {
tree.init(depth, new Tree(), new Tree());
stack.put(tree.leftChild, depth -1);
stack.put(tree.rghtChild, depth -1);
}
}
return result;
}
}
class Tree {
int payload;
Tree leftChild;
Tree rghtChild;
public Tree init(int payload, Tree leftChild, Tree rghtChild) {
this.leftChild = leftChild;
this.rghtChild = rghtChild;
this.payload = payload;
return this;
}
@Override
public String toString() {
return "Tree(" +payload+", "+ leftChild + ", " + rghtChild + ")";
}
}
class TreeStack {
Tree[][] tree;
int[][] depth;
int stack = 1;
int index = -1;
TreeStack() {
this.tree = new Tree[100][];
this.depth = new int[100][];
alloc(100_000);
--stack;
alloc(0);
}
boolean isEmpty() {
return index == -1;
}
void alloc(int size) {
tree[stack] = new Tree[size];
depth[stack] = new int[size];
}
void inc() {
if (tree[stack].length == ++index) {
if (tree[++stack] == null) alloc(2 * index);
index = 0;
}
}
void dec() {
if (--index == -1)
index = tree[--stack].length - 1;
}
void put(Tree tree, int depth) {
inc();
this.tree[stack][index] = tree;
this.depth[stack][index] = depth;
}
}
【问题讨论】:
-
也许你会得到一些见解using a profiler?
-
我尝试使用 VisualVM 分析代码,但它只告诉我 70% 的自我时间花费在
createTree1和createTree3的正文中。剩下的就是 Tree init。堆栈操作不到 10%。 -
您有什么理由编写自己的堆栈而不是使用
ArrayDeque?我仍然看到 AD 的迭代缓慢,但只是好奇。 -
这不仅仅是递归与迭代。迭代解决方案太复杂了,我认为它在处理堆栈上花费了很多时间。有可能做出更好的迭代解决方案。
-
@Donat 我会对不太复杂的迭代版本非常感兴趣,只要它不改变遍历顺序 (DFS)。