从Haskell程序中的堆栈溢出或堆耗尽中恢复

时间:2011-04-19 12:19:02

标签: exception haskell stack-overflow

我目前在Haskell中编写遗传算法,其中我的染色体是代表可执行系统的相当复杂的结构。

为了让我评估染色体的适应性,我必须运行evolution函数,该函数执行给定系统的一个计算周期。然后通过计算在系统没有变化之前可以应用evolution的次数来计算适应度(在这种情况下系统终止)。

现在的问题如下:一些系统可以无限长时间运行并且永远不会终止 - 我想惩罚那些(通过给予它们很少的分数)。我可以简单地对步数设置一定的限制,但它不能解决另一个问题。

我的一些系统执行指数计算(即使对于它们增长到巨大尺寸的渐变步骤的小值),它们也会导致ERROR - Control stack overflow。对于人类观察者来说,很明显它们永远不会终止,但算法无法知道它是如何运行和压碎的。

我的问题是:是否可以从这样的错误中恢复?我希望我的算法在遇到此问题后继续运行,并相应地调整染色体分数。

在我看来,最好的解决办法就是告诉程序:“嘿,试试这个,但如果你失败了就不要担心。我知道如何处理它”。但是我甚至不确定这是否可能。如果没有 - 还有其他选择吗?

4 个答案:

答案 0 :(得分:5)

在Haskell内部很难可靠地做到这一点 - 尽管在某些条件下GHC will raise exceptions for these conditions。 (你需要GHC 7)。

import Control.Exception

如果你真的只想捕获堆栈溢出,这是可能的,正如这个例子所示:

> handle (\StackOverflow -> return Nothing) $
              return . Just $! foldr (+) 0 (replicate (2^25) 1) 
Nothing

或者捕获任何异步异常(包括堆耗尽):

> handle (\(e :: AsyncException) -> print e >> return Nothing) $
              return . Just $! foldr (+) 0 (replicate (2^25) 1) 
stack overflow
Nothing

但是,这很脆弱。

或者,使用GHC flags,您可以在GHC编译的进程上强制执行最大堆栈(或堆)大小,如果超过这些限制则导致它被终止(GHC似乎没有最近的堆栈限制)

如果使用GHC编译Haskell程序(按照建议),则将其运行为:

$ ghc -O --make A.hs -rtsopts 

强制执行以下低堆限制:

$ ./A +RTS -M1M -K1M
Heap exhausted;

这需要GHC。 (同样,你不应该使用Hugs进行这类工作)。最后,您应该首先通过profiling in GHC确保您的程序使用过多的堆栈。

答案 1 :(得分:2)

我认为这里的一般解决方案是提供一种测量计算时间的方法,如果花费太多时间就将其杀死。您可以简单地将计数器添加到评估函数中(如果它是递归的),如果它降为零,则返回错误值 - 例如Nothing,否则它是Just result

这种方法可以通过显式计数参数以其他方式实现,例如将此计数器放入评估使用的monad中(如果您的代码是monadic),或者不明确地,通过在单独的线程中运行计算,该计算将在超时时被终止。

我宁愿使用任何纯粹的解决方案,因为它会更可靠。

答案 2 :(得分:1)

It seems to me like the best solution would be to tell the program: 
"Hey, try doing this, but if you fail don't worry. I know how to handle it"

在大多数语言中都是try/catch块。我不确定haskell中的等价物是什么,或者即使存在某些等价物。此外,我怀疑try/catch构造可以有效地捕获/处理堆栈溢出情况。

但是可以应用一些合理的约束来防止溢出发生吗?例如,也许您可​​以设置一个系统大小的上限,并监视每个系统从一次迭代到下一次迭代的接近边界的方式。然后,您可以强制执行一条规则,例如“如果在单个evolution系统上超出其上限或消耗了其先前分配与其上限之间剩余空间的50%以上,该系统将终止并且遭受分数惩罚“。

答案 3 :(得分:1)

对遗传算法的一种思考:染色体适应性的一部分是它们不会消耗太多的计算资源。您提出的问题将“太多资源”定义为崩溃运行时系统。这是一个相当随意和有点随机的措施。

知道它会增加evolve函数的复杂性,我仍然建议让这个函数知道染色体消耗的计算资源。这使你可以在它“吃得太多”并且过早死于“饥饿”时进行微调。它还可以让你根据染色体指数变化的速度来调整你的惩罚,因为只有几乎指数的染色体比具有极高分支因子的染色体更合适。