【问题标题】:Memory behavior of recursivity using Lambda使用 Lambda 递归的记忆行为
【发布时间】:2016-08-21 00:32:57
【问题描述】:

我正在使用以下代码对二叉树进行前序遍历:

        public void PreorderTraversal(Action<BinaryTreeNode<T>> act) {
            Action<BinaryTreeNode<T>> InnerTraverse = null;
            InnerTraverse = (node) => {
                if (node == null) return;
                act(node);
                InnerTraverse(node.Left);
                InnerTraverse(node.Right);
            };
            InnerTraverse(this.Root);
        }

从性能的角度来看,这种使用本地定义的 lambda 在树上递归的方法是否比将 InnerTraverse 函数简单地定义为 BinaryTree 类上的一个方法(这是定义 PreorderTraversal 函数本身的位置)更差?

【问题讨论】:

  • “复制”怎么办?任何闭包捕获变量都会导致隐藏闭包类的新实例。对于方法的每次调用都是如此,甚至对于在循环的语句块中找到的闭包也是如此。您担心哪种重复?你看过编译的 IL,例如通过 ildasm.exe,更好地了解编译代码中发生了什么?你做了什么研究?我不明白你会认为什么是“负责任的”和“幼稚的”。请提高您问题的清晰度。
  • @PeterDuniho 本质上,我想知道以这种方式使用 lambda 相对于在类级别简单地定义两个函数是否幼稚。我会改写这个问题。我可以衡量性能,但我不清楚使用 lambda 与类方法的含义。
  • 当然,不需要使用 lambda 表达式或任何类型的委托来正确遍历二叉树。因此,如果这是您的主要问题,那么是的……您发布的代码是错误的方法。在可能的情况下,应始终选择更简单的实现。具体细节可能会根据您的二叉树数据结构而有所不同;如果您需要有关遍历实现的具体帮助,您应该包含一个好的minimal reproducible example
  • @PeterDuniho 我正在探索这种方法来进一步封装遍历代码(而不是在 BinaryTree 类上使用私有方法[实际上是三个,因为有 Inorder 和 Postorder])。最终,我将编写一个非递归函数来完成此任务。这个问题的起源只是为了理解递归使用 lambda 的含义,这不是我以前做过的事情。
  • 好吧,PreorderTraversal() 方法(大概)只被调用一次,所以至少你不会多次创建InnerTraverse。但是您根本不需要委托,只需抽象用于每个节点的act 委托。在其他场景中,您可能希望抽象出如何检索左右节点,这也可以通过传递给遍历方法的委托来完成。根据您迄今为止提供的信息,我认为无需将方法的代码包装在另一个方法中。

标签: c# memory lambda closures


【解决方案1】:

编译器会将示例方法翻译成如下内容:

class Closure
{
     public Action<BinaryTreeNode<T>> act;
     public Action<BinaryTreeNode<T>> InnerTraverse;

     public void InnerTraverseFunc(BinaryTreeNode<T> node)
     {
         if (node == null) return;
         this.act(node);
         this.InnerTraverse(node.Left);
         this.InnerTraverse(node.Right);
     }
}

public void PreorderTraversal(Action<BinaryTreeNode<T>> act)
{
    var c = new Closure();
    c.act = act;
    c.InnerTraverse = new Action<BinaryTreeNode<T>>(c.InnerTraverseFunc);
    c.InnerTraverse(this.Root);
}

如您所见,成本是每个 T 一个新类型,每个根方法调用 2 个堆分配,加上使用委托调用与使用常规静态递归方法的直接调用。

IMO 额外的运行时成本并没有那么大,但同时由于在这种情况下使用递归 lambda 绝对没有任何好处,最好避免。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 2012-11-12
    • 2012-09-27
    • 1970-01-01
    • 2018-09-08
    • 2013-01-07
    相关资源
    最近更新 更多