C# 防止在C中创建stackframe#
我只是在读csharp中递归函数的性能。我读到递归函数很昂贵,因为每次调用时都会创建堆栈帧 有没有办法防止在csharp中创建堆栈帧?我环顾四周,但似乎找不到任何迹象表明堆栈帧可以被抑制用于特定调用 我希望我可以在代码中添加一些东西,可能是一个如下属性:C# 防止在C中创建stackframe#,c#,C#,我只是在读csharp中递归函数的性能。我读到递归函数很昂贵,因为每次调用时都会创建堆栈帧 有没有办法防止在csharp中创建堆栈帧?我环顾四周,但似乎找不到任何迹象表明堆栈帧可以被抑制用于特定调用 我希望我可以在代码中添加一些东西,可能是一个如下属性: [SurpressStackFrame()] find(int id, Node currentNode) { if(currentNode.id == id) { return true; } return
[SurpressStackFrame()]
find(int id, Node currentNode) {
if(currentNode.id == id) {
return true;
}
return find(id, currentNode.child);
}
(顺便说一句,我知道在这个例子中我不是在看多个孩子,这只是假设)。它被称为。NET运行时支持它(因为F#需要它),但C#编译器(甚至是“新的”Roslyn编译器)不支持它。请参阅。它被称为。NET运行时支持它(因为F#需要它),但C#编译器(甚至是“新的”Roslyn编译器)不支持它。请参阅。在大多数情况下,正在创建的堆栈帧不是代码的性能瓶颈。但是,如果方法中存在高级别的递归,则可能希望避免扩大堆栈。这称为尾部调用优化,通过在调用内部方法之前释放当前堆栈帧来完成 在您的特定示例中,尾部调用可以应用于
return
语句,因为内部方法调用的返回值会立即返回给调用方,因此不需要单独的堆栈框架
唯一的问题是,C#不支持尾部调用(目前,可能在不久的将来也不支持)。但是,CLR确实支持它,因此如果可以,可以选择像F#这样更注重递归的语言
您还可以使用其他几个选项—使用CIL动态创建一个方法,该方法在最后进行尾部调用(但是,我不确定调用动态方法的代价是否比在C#中调用普通递归方法更好)
在这种情况下,最好的选择是根本不要使用递归。您提供的方法可以很容易地重写,而无需使用递归,当然,大多数(如果不是所有)递归方法都可以这样重写:
bool find(int id, Node currentNode)
{
while(currentNode.id != id)
{
currentNode = currentNode.child;
}
return true;
}
在大多数情况下,正在创建的堆栈帧不是代码的性能瓶颈。但是,如果方法中存在高级别的递归,则可能希望避免扩大堆栈。这称为尾部调用优化,通过在调用内部方法之前释放当前堆栈帧来完成 在您的特定示例中,尾部调用可以应用于
return
语句,因为内部方法调用的返回值会立即返回给调用方,因此不需要单独的堆栈框架
唯一的问题是,C#不支持尾部调用(目前,可能在不久的将来也不支持)。但是,CLR确实支持它,因此如果可以,可以选择像F#这样更注重递归的语言
您还可以使用其他几个选项—使用CIL动态创建一个方法,该方法在最后进行尾部调用(但是,我不确定调用动态方法的代价是否比在C#中调用普通递归方法更好)
在这种情况下,最好的选择是根本不要使用递归。您提供的方法可以很容易地重写,而无需使用递归,当然,大多数(如果不是所有)递归方法都可以这样重写:
bool find(int id, Node currentNode)
{
while(currentNode.id != id)
{
currentNode = currentNode.child;
}
return true;
}
你要找的是叫尾叫尾叫 有一条尾巴。指令,但C#编译器从不使用它。总之,这只是JIT编译器的提示,JIT编译器足够聪明,可以在可能的情况下编译常规递归方法调用作为尾部调用(在您发布的示例中应该是可能的)。与其他JIT优化一样,它只发生在发布版本中,而不发生在调试中 您可以进行一个简单的测试:
class Program
{
static void Main()
{
TailCall(0);
}
private static void TailCall(int i)
{
Console.WriteLine(i);
TailCall(++i);
}
}
在调试中,它将抛出StackOverflowException,在发布时它将无限旋转
您可以在中的.NET中找到有关尾部调用优化的更多信息。您正在寻找的是所谓的尾部调用 有一条尾巴。指令,但C#编译器从不使用它。无论如何,这只是对JIT编译器的一个提示,JIT编译器足够聪明,可以在可能的情况下将常规递归方法调用编译为尾部调用(在您发布的示例中应该是可能的)。与其他JIT优化一样,它只发生在发布版本中,而不发生在调试中 您可以进行一个简单的测试:
class Program
{
static void Main()
{
TailCall(0);
}
private static void TailCall(int i)
{
Console.WriteLine(i);
TailCall(++i);
}
}
在调试中,它将抛出StackOverflowException,在发布时它将无限旋转
您可以在中的.NET中找到有关尾部调用优化的更多信息。它被称为您想要的尾部调用递归。NET(运行时支持它)。如果您关心性能,为什么不将其重写为一个迭代函数呢。@CSharpie我只是想知道是否有一种方法可以“吃我的蛋糕,吃它”。)这就是所谓的尾部调用递归。NET(运行时支持它)。如果您关心性能,为什么不将其重写为一个迭代函数呢。@CSharpie我只是想知道是否有一种方法可以“吃我的蛋糕,吃它”。)谢谢,我不知道它的术语。但是如果JIT认为合适的话,它仍然会这样做,对吗?即使没有显式的.tail调用。@Evk是的,
.tail
只是一个提示。。。JIT甚至应该调用其他方法。。。我不知道它有多积极。谢谢,我不知道它的术语。但是如果JIT认为合适的话,它仍然会这样做,对吗?即使没有显式的.tail调用。@Evk是的,.tail
只是一个提示。。。JIT甚至应该调用其他方法。。。我不知道它有多积极。