【问题标题】:segmentation fault for deep recursion upon going from python 2.6 to 2.7从 python 2.6 到 2.7 时深度递归的分段错误
【发布时间】:2013-10-04 10:21:56
【问题描述】:

我有一个简单的递归程序来查找连接的子图。 该代码通过从图中每个未访问的顶点遍历所有边(递归地)并在过程中用“ingraph=False”标记已访问的边来工作。 (图始终是无向无权图)。

我遇到的问题是,对于大型图(具有约 100,000 个顶点的子图)python(-2.7) 返回分段错误。 但这在 python-2.6 中工作得很好(现在仍然如此)。

有人可以向我解释一下两者之间发生了什么变化(或者可能完全是其他事情)吗?有没有办法用 python-2.7 修复它(在某些时候迁移到 python-3 时最好也不会中断)?还是应该用非递归的方式重写?

谢谢!

这里是源更新:有关非递归解决方案,请参阅下面的更新 3

def floodfill(g):
  for v in g.vertices: v.ingraph = True
  sys.setrecursionlimit(g.size)
  subgraph = 1
  for v in g.vertices:
    if v.ingraph:
      v.ingraph = False
      v.subgraph = subgraph
      g.floodfill_search(v,subgraph)
      subgraph += 1

def floodfill_search(g,v,subgraph):
  for n in v.neighbors:
    if n.ingraph:
      n.ingraph = False
      n.subgraph = subgraph
      g.floodfill_search(n,subgraph)

-------- 更新 --------

我做了一个小的递归测试,它为 3 台不同的计算机给出了 ~16,000、~24,000 和 ~28,000 的递归限制。此外,对于一台电脑来说,结果甚至不是恒定的。运行两次测试给出的限制略有不同。例如对于第二个,我发现结果介于 23800 和 23819 之间。

#!/usr/bin/python
import sys
def callme(i):
  print(i)
  i+=1
  callme(i)

sys.setrecursionlimit(int(1e6))
callme(0)

我真的不知道指的是哪个“C 堆栈”,据我所知,在 C 中没有实现默认的“堆栈”。在 C++ 中有堆栈,但它没有相同的限制。以下 C++ 示例运行良好(至少最多 1M 次推送)。

#include <iostream>   
#include <stack>
using namespace std;

int main () {
  stack<int> s;
  for(int i=0;i<1000000;i++) {
    cout << "push " << i << endl;
    s.push(i);
  }
}

以下 C 代码也更深入(大约 10 倍,~262,000)

#include "stdio.h"
void callme(i) { 
  printf("calling %d\n",i);
  callme(++i);
}

int main() {
  int i=0;
  callme(i);
}

---- 更新 2 -----

好吧,这显然是 python 的意图。迫使程序员避免深度递归。 http://neopythonic.blogspot.ch/2009/04/tail-recursion-elimination.html

无论如何,我现在认为最好迭代地重写它。但是我可能会在 C++ 中重新开始,使用一些图论库,比如 boost 库。如果我无论如何都要重写它,我还不如把它做好。

尽管如此,我仍然希望任何 cmets 能够理解为什么会在这些特定尺寸下发生这种情况。

---- 更新 3 -----

这至少是一个快速而肮脏的 python 重写。 脏因为它是 O(N^2) 因为最后一行。 应该有一个更好的 O(N) 解决方案,通过跟踪哪些顶点没有被访问,但没有这么快看到它的列表,这适用于我的应用程序。

def floodfill_nonrecursive(g):
    for v in g.vertices: v.ingraph = True
    start = g.vertices
    subg = 1
    while start:
      q = [start[0]]
      while q:
        v = q.pop()
        v.ingraph = False
        v.subgraph = subg
        for n in v.neighbors:
          if n.ingraph:
            n.ingraph = False
            q.append(n)
      subg += 1
      start = [v for v in g.vertices if v.ingraph]

【问题讨论】:

    标签: python python-2.7 recursion graph-theory python-2.6


    【解决方案1】:

    因为您的 Python 使用 C 堆栈,所以它已溢出。 setrecursionlimit 无法扩展 cstack 大小。它只是限制在 cstack 溢出之前引发异常。 Python 的递归深度有限。 2.6 的成功只是幸运。

    您应该将代码从递归更改为迭代样式或使用无堆栈 python(或 PyPy)。 阅读http://docs.python.org/2/library/sys.html#sys.setrecursionlimit

    【讨论】:

    • 好吧,我没想到它这么小,我认为一层递归应该花费一个指针。我运行了一个小型测试,给定一台电脑的递归限制约为 16,000,另一台电脑的递归限制约为 24,000,另一台电脑的递归限制约为 28,000。此外,对于一台电脑来说,结果甚至不是恒定的。运行两次测试给出的限制略有不同。 (见更新的问题)。知道这是怎么发生的吗?
    【解决方案2】:

    您可能会在 Python 实现的某个地方使用深度递归溢出堆栈。您可以尝试使用 sys.setrecursionlimit 更改堆栈部门

    另一种可能性是您耗尽了动态内存。递归通常更费力。

    您在 Python 2.6 上的运气更好。以前的版本需要更少的内存用于您的算法。

    Python 不是函数式语言,也不优化尾递归。迭代地重写算法可能是更好的方法。

    【讨论】:

    • 我将递归限制设置为图形大小 'sys.setrecursionlimit(g.size)' 但它仍然崩溃。
    • 另外,可以使用stackless python或具有stackless功能的pypy。
    猜你喜欢
    • 2015-02-28
    • 1970-01-01
    • 2021-12-11
    • 2020-06-25
    • 2017-02-20
    • 1970-01-01
    • 2017-08-24
    • 1970-01-01
    • 2016-12-10
    相关资源
    最近更新 更多