简体   繁体   English

在 C# 中使用递归

[英]Using Recursion in C#

Are there any general rules when using recursion on how to avoid stackoverflows?在使用递归时是否有任何一般规则来避免stackoverflows?

How many times you will be able to recurse will depend on:您能够递归多少次取决于:

  • The stack size (which is usually 1MB IIRC, but the binary can be hand-edited; I wouldn't recommend doing so)堆栈大小(通常为 1MB IIRC,但二进制文件可以手动编辑;我不建议这样做)
  • How much stack each level of the recursion uses (a method with 10 uncaptured Guid local variables will be take more stack than a method which doesn't have any local variables, for example)每个级别的递归使用多少堆栈(例如,具有 10 个未捕获的Guid局部变量的方法将比没有任何局部变量的方法占用更多的堆栈)
  • The JIT you're using - sometimes the JIT will use tail recursion, other times it won't.您正在使用的 JIT - 有时 JIT使用尾递归,有时则不会。 The rules are complicated and I can't remember them.规则很复杂,我不记得了。 (There's a blog post by David Broman back from 2007 , and an MSDN page from the same author/date , but they may be out of date by now.) David Broman 从 2007 年开始发表博客文章,以及同一作者/日期的 MSDN 页面,但它们现在可能已经过时了。)

How to avoid stack overflows?如何避免堆栈溢出? Don't recurse too far:) If you can't be reasonably sure that your recursion will terminate without going very far (I'd be worried at "more than 10" although that's very safe) then rewrite it to avoid recursion.不要递归太远:) 如果你不能合理地确定你的递归会在不走很远的情况下终止(我会担心“超过 10”,尽管这很安全)然后重写它以避免递归。

It really depends on what recursive algorithm you're using.这实际上取决于您使用的递归算法。 If it's simple recursion, you can do something like this:如果它是简单的递归,你可以这样做:

public int CalculateSomethingRecursively(int someNumber)
{
    return doSomethingRecursively(someNumber, 0);
}

private int doSomethingRecursively(int someNumber, int level)
{
    if (level >= MAX_LEVEL || !shouldKeepCalculating(someNumber))
        return someNumber;
    return doSomethingRecursively(someNumber, level + 1);
}

It's worth noting that this approach is really only useful where the level of recursion can be defined as a logical limit.值得注意的是,这种方法实际上只在递归级别可以定义为逻辑限制的情况下才有用。 In the case that this cannot occur (such as a divide and conquer algorithm), you will have to decide how you want to balance simplicity versus performance versus resource limitations.如果这不可能发生(例如分而治之算法),您将必须决定如何平衡简单性、性能和资源限制。 In these cases, you may have to switch between methods once you hit an arbritrary pre-defined limit.在这些情况下,一旦达到任意预定义的限制,您可能必须在方法之间切换。 An effective means of doing this that I have used in the quicksort algorithm is to do it as a ratio of the total size of the list.我在快速排序算法中使用的一种有效方法是将其作为列表总大小的比率。 In this case, the logical limit is a result of when conditions are no longer optimal.在这种情况下,逻辑限制是条件不再最优的结果。

I am not aware of any hard set to avoid stackoverflows.我不知道有任何硬性设置可以避免堆栈溢出。 I personally try to ensure -我个人尽量确保——
1. I have my base cases right. 1. 我的基本情况是正确的。
2. The code reaches the base case at some point. 2. 代码在某个时候达到了基本情况。

If you're finding yourself generating that many stack frames, you might want to consider unrolling your recursion into a loop.如果您发现自己生成了这么多堆栈帧,您可能需要考虑将递归展开为循环。

Especially if you are doing multiple levels of recursion (A->B->C->A->B...) you might find that you can extract one of those levels into a loop and save yourself some memory.特别是如果您正在执行多个级别的递归(A->B->C->A->B...),您可能会发现您可以将其中一个级别提取到循环中并为自己节省一些 memory。

The normal limit, if not much is left on the stack between successive calls, is around 15000-25000 levels deep.正常的限制,如果在连续调用之间的堆栈上没有多少剩余的话,大约是 15000-25000 层深。 25% of that if you are on IIS 6+.如果您使用的是 IIS 6+,则为 25%。

Most recursive algorhitms can be expressed iteratively.大多数递归算法都可以迭代表示。

There are various way to increase allocated stack space, but I'll rather let you find an iterative version first.有多种方法可以增加分配的堆栈空间,但我宁愿让你先找到一个迭代版本。 :) :)

I wrote a short article about this here .在这里写了一篇关于这个的短文。 Basically, I pass an optional parameter called, depth, adding 1 to it each time I go deeper into it.基本上,我传递了一个名为 depth 的可选参数,每次将 go 深入其中时,都会向其添加 1。 Within the recursive method I check the depth for a value.在递归方法中,我检查一个值的深度。 If it is greater than the value I set, I throw an exception.如果它大于我设置的值,我会抛出异常。 The value (threshold) would be dependent on your applications needs.该值(阈值)将取决于您的应用程序需求。

Other than having a reasonable stack size and making sure you divide and conquer your problem such that you continually work on a smaller problem, not really.除了有一个合理的堆栈大小并确保你分而治之,这样你就可以不断地处理一个较小的问题,而不是真的。

I just thought of tail-recursion, but it turned out, that C# does not support it.我只是想到了尾递归,但事实证明,C# 不支持它。 However the.Net-Framework seems to support it:但是.Net-Framework 似乎支持它:

http://blogs.msdn.com/abhinaba/archive/2007/07/27/tail-recursion-on-net.aspx http://blogs.msdn.com/abhinaba/archive/2007/07/27/tail-recursion-on-net.aspx

The default stack size for a thread is 1 MB, if you're running under the default CLR.如果您在默认 CLR 下运行,则线程的默认堆栈大小为 1 MB。 However, other hosts may change that.但是,其他主机可能会改变这一点。 For example the ASP host changes the default to 256 KB.例如,ASP 主机将默认值更改为 256 KB。 This means that you may have code that runs perfectly well under VS, but breaks when you deploy it to the real hosting environment.这意味着您的代码可能在 VS 下运行良好,但在将其部署到真实托管环境时会中断。

Fortunately you can specify a stack size, when you create a new thread by using the correct constructor.幸运的是,当您使用正确的构造函数创建新线程时,您可以指定堆栈大小。 In my experience it is rarely necessary, but I have seen one case where this was the solution.根据我的经验,很少有必要,但我见过一个案例,这是解决方案。

You can edit the PE header of the binary itself to change the default size.您可以编辑二进制文件本身的 PE header 以更改默认大小。 This is useful if you want to change the size for the main thread.如果您想更改主线程的大小,这很有用。 Otherwise I would recommend using the appropriate constructor when creating threads.否则,我建议在创建线程时使用适当的构造函数。

Remember, if you have to ask about system limits, then you are probably doing something horribly wrong.请记住,如果您必须询问系统限制,那么您可能做错了什么。

So, if you think you might get a stack overflow in normal operation then you need to think of a different approach to the problem.因此,如果您认为在正常操作中可能会出现堆栈溢出,那么您需要考虑一种不同的方法来解决问题。

It's not difficult to convert a recursive function into an iterative one, especially as C# has the Generic::Stack collection.将递归的 function 转换为迭代的并不困难,尤其是 C# 具有 Generic::Stack 集合。 Using the Stack type moves the memory used into the program's heap instead of the stack.使用 Stack 类型将使用的 memory 移动到程序的堆而不是堆栈中。 This gives you the full address range to store the recursive data.这为您提供了存储递归数据的完整地址范围。 If that isn't enough, it's not too difficult to page the data to disk.如果这还不够,将数据分页到磁盘并不太难。 But I'd seriously consider other solutions if you get to this stage.但如果你到了这个阶段,我会认真考虑其他解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM