防止在C#

时间:2017-03-26 10:57:24

标签: c#

我刚刚在csharp中对递归函数的性能进行了一些阅读。我读到由于每次调用时都会创建堆栈帧,递归函数很昂贵。

有没有办法防止在csharp中创建堆栈帧?我环顾四周,但似乎无法找到任何暗示特定呼叫可以抑制堆栈帧的内容。

我希望有一些东西可以添加到代码中,也许是如下属性:

[SurpressStackFrame()]
find(int id, Node currentNode) { 

   if(currentNode.id == id) {
      return true;
   }
   return find(id, currentNode.child);
}

(p.s。我知道我在这个例子中并没有看多个孩子,这只是假设)。

3 个答案:

答案 0 :(得分:2)

在大多数情况下,创建的堆栈帧不是代码的性能瓶颈。但是,如果方法中存在高级别的递归,则可能希望避免扩大堆栈。这称为尾调用优化,通过在调用内部方法之前释放当前堆栈帧来完成。

在您的具体示例中,尾调用可以应用于return语句,因为内部方法调用的返回值会立即返回给调用者,因此不需要单独的堆栈帧。

唯一的问题是C#不支持尾调用(目前并且可能不会在不久的将来)。但是,CLR确实支持它,所以如果可以,你可以选择像F#这样更专注于递归的语言。

还有其他一些选项 - 动态创建一个使用CIL的方法,最后进行尾调用(但是,我不确定调用动态方法的费用是否比调用正常的递归方法更好) C#)。

这种情况下的最佳选择 - 根本不使用递归。您提供的方法可以轻松地重写,而不需要使用递归,当然,大多数(如果不是所有)递归方法都可以通过这种方式重写:

bool find(int id, Node currentNode)
{ 
    while(currentNode.id != id)
    {
        currentNode = currentNode.child;
    }
    return true;
}

答案 1 :(得分:1)

它被称为tail call。它受.NET运行时支持(因为F#需要它),但是C#编译器(甚至是#34; new" Roslyn编译器)不支持它。请参阅request on the github

答案 2 :(得分:1)

您正在寻找的是尾调用。

有尾巴。 IL中的指令,但C#编译器从不使用它。无论如何,它只是对JIT编译器的一个提示,并且JIT编译器足够聪明,可以在可能的情况下将常规递归方法调用编译为尾调用(在您发布的示例中应该可以)。与其他JIT优化一样,它只发生在发布版本中,而不是在调试中。

您可以进行简单的测试:

class Program
{
    static void Main()
    {
        TailCall(0);
    }
    private static void TailCall(int i)
    {
        Console.WriteLine(i);
        TailCall(++i);
    }
}

在debug中,它将抛出一个StackOverflowException,在Release中它将无限旋转。

您可以在this blog post中找到有关.NET中尾调用优化的更多信息。