我一直在努力,现在找到了几个小时的回答这个问题,在网络上,并在这个网站,我不能令人信服。
据我所知,.NET 1MB分配给应用程序,而且它最好通过重新编码,而不是强迫堆栈大小,以避免堆栈溢出。
我工作的一个“最短路径”的应用程序,以约3000个节点,此时它溢出的伟大工程起来。 下面是导致问题的方法:
public void findShortestPath(int current, int end, int currentCost)
{
if (!weight.ContainsKey(current))
{
weight.Add(current, currentCost);
}
Node currentNode = graph[current];
var sortedEdges = (from entry in currentNode.edges orderby entry.Value ascending select entry);
foreach (KeyValuePair<int, int> nextNode in sortedEdges)
{
if (!visited.ContainsKey(nextNode.Key) || !visited[nextNode.Key])
{
int nextNodeCost = currentCost + nextNode.Value;
if (!weight.ContainsKey(nextNode.Key))
{
weight.Add(nextNode.Key, nextNodeCost);
}
else if (weight[nextNode.Key] > nextNodeCost)
{
weight[nextNode.Key] = nextNodeCost;
}
}
}
visited.Add(current, true);
foreach (KeyValuePair<int, int> nextNode in sortedEdges)
{
if(!visited.ContainsKey(nextNode.Key) || !visited[nextNode.Key]){
findShortestPath(nextNode.Key, end, weight[nextNode.Key]);
}
}
}//findShortestPath
作为参考,节点类具有一个成员:
public Dictionary<int, int> edges = new Dictionary<int, int>();
图形[]是:
private Dictionary<int, Node> graph = new Dictonary<int, Node>();
我试图opimize使得它没有携带不是从一个迭代(递归?)需要在未来的任何行李多的代码,但与1-9之间为每个节点100K节点图边它会打的1MB限制很快。
反正,我是新来的C#代码优化,如果有人可以给我一些指点( 不是这样 ),我将不胜感激。
前阵子我在我的博客探讨这个问题。 或者说,我摸索出了相关的问题:你如何找到一个二叉树的深度,而无需使用递归? 递归树深度溶液是微不足道的,但吹堆如果树高度不平衡。
我的建议是研究解决这个问题的简单方法,然后决定哪些人,如果有的话,可以适应你稍微更复杂的算法。
请注意,这些文章中的例子是在JScript完全放弃。 然而,它不应该是很难使之适应于C#。
在这里,我们通过定义问题开始。
http://blogs.msdn.com/ericlippert/archive/2005/07/27/recursion-part-one-recursive-data-structures-and-functions.aspx
在解决方案的第一次尝试是典型的技术,你可能会采取:定义一个明确的堆栈; 使用它,而不是依赖于操作系统和编译器执行堆栈为您服务。 这是当面对这个问题大多数人做的。
http://blogs.msdn.com/ericlippert/archive/2005/08/01/recursion-part-two-unrolling-a-recursive-function-with-an-explicit-stack.aspx
与解决方案的问题是,这是一个有点乱。 我们可以有过之而无不及简单地使我们自己的堆栈。 我们可以让我们自己的小领域特定的虚拟机都有自己的堆分配栈,然后通过编写一个程序,它针对的是机器解决问题! 这是比它听起来其实更容易; 机器的操作可能会非常高的水平。
http://blogs.msdn.com/ericlippert/archive/2005/08/04/recursion-part-three-building-a-dispatch-engine.aspx
最后,如果你真的惩罚馋嘴(或编译器的开发者)可以在继续传递方式,从而消除了对堆栈有必要在所有重写你的程序:
http://blogs.msdn.com/ericlippert/archive/2005/08/08/recursion-part-four-continuation-passing-style.aspx
http://blogs.msdn.com/ericlippert/archive/2005/08/11/recursion-part-five-more-on-cps.aspx
http://blogs.msdn.com/ericlippert/archive/2005/08/15/recursion-part-six-making-cps-work.aspx
CPS是通过在一堆代表之间的关系编码它移动的隐含堆栈数据结构关闭系统堆栈和到堆上的一个特别聪明的方式。
这里是我所有的递归的文章:
http://blogs.msdn.com/ericlippert/archive/tags/Recursion/default.aspx
经典的技术,以避免深递归栈潜水是简单地通过用合适的列表数据结构反复写算法和管理自己的“堆栈”避免递归。 最有可能你会在这里需要这种方法给您输入的庞大规模设置。
你可以将代码转换为使用“工作队列”,而不是递归。 沿着下面的伪代码的东西:
Queue<Task> work;
while( work.Count != 0 )
{
Task t = work.Dequeue();
... whatever
foreach(Task more in t.MoreTasks)
work.Enqueue(more);
}
我知道那是神秘的,但它是你需要做的基本概念。 由于您只获得3000个节点与当前的代码,你会得到最好的12〜15K不带任何参数。 所以,你需要彻底杀灭递归。
是您的节点结构或一类? 如果是前者,使它成为一个类,以便它在堆中而不是在栈上分配。
我会首先确认你实际上溢出堆栈:你真正看到一个StackOverflowException获得由运行时抛出。
如果这是事实确实如此,你有几种选择:
- 修改您的递归函数,这样.NET运行时可以将其转换成尾递归函数 。
- 修改您的递归函数,以便它是迭代的,使用自定义的数据结构,而不是管理堆栈。
选项1并不总是可能的,并假定CLR用来生成尾递归调用的规则将在未来保持稳定。 主要好处是,在可能的情况,尾递归实际上是写递归算法而不牺牲清晰度的一种方便的方法。
选项2是一个更多的工作,但并不对CLR的实施敏感,并且可以为任何递归算法(其中尾递归可能不总是可能的)来实现。 一般情况下,你需要收集和传递一些循环迭代之间的状态信息,对如何“展开”的数据结构,采用堆栈的地点信息一起(通常是一个List <>或堆栈<>)。 展开递归迭代成的一种方式是通过连续传递模式。
在C#尾递归更多资源:
为什么不.NET / C#优化尾调用递归?
http://geekswithblogs.net/jwhitehorn/archive/2007/06/06/113060.aspx
我首先要确保我知道为什么我得到一个堆栈溢出。 它实际上是因为递归的? 递归方法没有太大的投入到堆栈。 也许是因为节点的存储?
另外,顺便说一句,我没有看到end
参数千变万化。 这表明它并不需要是一个参数,每个堆栈帧上承载。
文章来源: How do I avoid changing the Stack Size AND avoid getting a Stack Overflow in C#