递归比迭代更快

时间:2013-09-17 06:59:01

标签: c# performance recursion iteration complexity-theory

我在C#中实现了一个四叉树,并且遇到了一个奇怪的现象,其中递归似乎比迭代更好,尽管它看起来应该相反。

我的节点看起来像这样:

class QuadNode
{
    private QuadNode topLeft;
    private QuadNode topRight;
    private QuadNode bottomRight;
    private QuadNode bottomLeft;
    // other fields...
}

为了遍历树,我使用了以下递归方法,我在根节点上调用:

Traverse()
{
    // visit 'this'

    if (topLeft != null)
        topLeft.Traverse();
    if (topRight != null)
        topRight.Traverse();
    if (bottomRight != null)
        bottomRight.Traverse();
    if (bottomLeft != null)
        bottomLeft.Traverse();
}

主要是出于兴趣,我试图创建一个遍历树的迭代方法。

我在每个节点添加了以下字段:private QuadNode next,当我创建树时,我使用队列执行广度优先遍历,将每个节点的next字段链接到下一个节点排队。基本上我从树的节点创建了一个单链表 此时,我可以使用以下方法遍历树:

Traverse()
{
    QuadNode node = this;
    while (node != null)
    {
        // visit node

        node = node.next;
    }
}

在测试了每种方法的性能之后,我非常惊讶地发现迭代版本一致且明显慢于递归版本。我已经在巨大的树木和小树上进行了测试,递归方法总是更快。 (我使用Stopwatch进行基准测试)
我已经确认两种方法都成功遍历整个树,并且迭代版本只按计划访问每个节点一次,因此它们之间的链接不是问题。

我觉得迭代版本会表现得更好......这可能是什么原因造成的?我是否忽略了为什么递归版本更快的明显原因?

我正在使用Visual Studio 2012并在Release,Any CPU下编译(更喜欢32位未选中)。

修改
我开了一个新项目并创建了一个简单的测试,这也证实了我的结果 这是完整的代码:http://pastebin.com/SwAsTMjQ
代码没有评论,但我认为这是非常自我记录的。

2 个答案:

答案 0 :(得分:4)

缓存局部性正在扼杀速度。尝试:

public void LinkNodes()
{
    var queue = new Queue<QuadNode>();
    LinkNodes(queue);

    QuadNode curr = this;

    foreach (var item in queue)
    {
        curr.next = item;
        curr = item;
    }
}

public void LinkNodes(Queue<QuadNode> queue)
{
    queue.Enqueue(this);

    if (topLeft != null)
        topLeft.LinkNodes(queue);
    if (topRight != null)
        topRight.LinkNodes(queue);
    if (bottomRight != null)
        bottomRight.LinkNodes(queue);
    if (bottomLeft != null)
        bottomLeft.LinkNodes(queue);
}

现在,迭代版本应该比递归版本快30/40%。

缓慢的原因是你的迭代算法会先进入广度而不是深度优先。您创建了元素Depth First,因此它们在内存中排序为Depth First。我的算法创建了遍历列表Depth First。

(请注意,我在Queue中使用了LinkNodes(),以便更容易理解,但实际上您可以在不使用的情况下执行此操作。

public QuadNode LinkNodes(QuadNode prev = null)
{
    if (prev != null)
    {
        prev.next = this;
    }

    QuadNode curr = this;

    if (topLeft != null)
        curr = topLeft.LinkNodes(curr);

    if (topRight != null)
        curr = topRight.LinkNodes(curr);

    if (bottomRight != null)
        curr = bottomRight.LinkNodes(curr);

    if (bottomLeft != null)
        curr = bottomLeft.LinkNodes(curr);

    return curr;
}

答案 1 :(得分:0)

查看你的代码,两种方法看起来都是一样的,但是在递归中你在“循环”中访问了4个节点,这意味着你不会在3次测试之间“跳”,而在迭代中你会“跳” “每次运行到循环的开头。 我想如果你想看到几乎相似的行为,你必须将迭代循环展开为:​​

Traverse(int depth)
{
    QuadNode node = this;
    while (node != null)
    {
        // visit node

        node = node.next;
        if (node!=null) node=node.next;
        if (node!=null) node=node.next;
        if (node!=null) node=node.next;

    }
}