24

我第一次在我正在编写的 Haskell 程序中遇到无限循环。我已经将它缩小到一个非常具体的代码部分,但我似乎无法准确指出我在哪里有一个非终止递归定义。我对 GHCi 中的 :trace 和 :history 有点熟悉,但问题是我的代码的某些分支涉及 a 的相当多的递归修改,Data.Map.Map因为地图x是通过adjust在地图中x'基于值的某些东西获得的在另一张地图上取决于x'. 细节在这里并不重要,但正如您可能知道的那样,如果这以一种交织在一起的递归方式发生,我的调用历史将完全陷入 map lookupadjustments 和insertions 所涉及的所有各种比较中。

谁能推荐一种更有效的方法来定位无限循环?例如,将调用历史限制为来自单个源文件的调用会很有帮助。

4

5 回答 5

15

我很惊讶没有人提到所有 Haskell 性能问题都会得到的普遍响应(无限运行时是“性能问题”的一个相当极端的例子):分析!

我只是能够使用分析快速识别无限循环。为了完整起见,使用 编译-prof -fprof-auto,然后运行程序足够长的时间,以使有问题的函数应该在分析统计中明显。例如,我希望我的程序在 1 秒内完成,所以我让分析器运行大约 30 秒,然后用 Ctrl+C 终止我的程序。(注意:分析保存增量结果,因此即使您在程序运行完成之前终止程序,您仍然可以获得有意义的数据。编辑除非它没有。

在 .prof 文件中,我找到了以下块:

                                                 individual      inherited
COST CENTRE         MODULE     no.    entries   %time  %alloc   %time %alloc
...
primroot.\          Zq         764          3    10.3    13.8    99.5  100.0
 primroot.isGen     Zq         1080   50116042    5.3     6.9    89.2   86.2
  primroot.isGen.\  Zq         1087   50116042   43.4    51.7    83.8   79.3
   fromInteger      ZqBasic    1088          0   40.4    27.6    40.4   27.6

因此,有 5000 万个条目primroot.isGen,而下一个被调用次数最多的函数只有 1024 个调用。此外,99.5% 的运行时间都花在了计算primroot上,这似乎很可疑。我检查了那个函数并很快发现了错误,在我的例子中是一个简单的错字:(`div` foo)而不是(div foo).

我认为值得注意的是,GHC 警告不会发现这个问题,也不会发现-fbreak-on-exceptions. 代码库很大;试图通过插入调试语句(任何类型的)来追踪问题并没有让我到任何地方。我也没有成功使用 GHCi 调试器,因为历史基本上不存在,而且 HPC 没有显示任何有用的信息。

于 2014-04-13T18:47:30.123 回答
11

正如 ShiDoiSi 所说,“通过眼睛”解决往往是最成功的方法。

如果您在相同的函数中绑定到不同的类似命名的变量 x、x' 等,您可以尝试在文件顶部启用警告:

{-# OPTIONS -Wall #-} 

如果问题是您绑定到错误的事物并进行失控递归,这可能会帮助您发现它 - 例如,通过指示意外使用阴影

于 2011-03-17T12:43:00.290 回答
7

确保您已完全使用 GHCi 调试器,包括设置 -fbreak-on-exception(如果您得到<<loop>>,这很有用,是吗?)并确保您已尝试过 Stephen 的使用 GHC 警告的建议。

如果这些失败(GHCi 调试器真的不应该“失败”,这只是解释数据的问题)然后尝试在循环案例上运行HPC,这样您就可以直观地看到没有被评估的分支和值,如果它正在循环那么应该完成的事情可能甚至没有被评估,并且将显示在标记的 HTML 中。

于 2011-03-17T15:55:22.463 回答
7

我正在进行一个漫长的调试会话,以查找无限循环的原因。我已经非常接近了,这是对我帮助最大的。假设您的循环是由以下原因引起的:

...
x1 = f1 x2 y
x2 = f2 z x3
x3 = f3 y x1
...

所以x1依赖x2,x2依赖x3,x3又依赖x1。坏的!

在 f1、f2、f3 的定义中添加跟踪函数。就像是:

f1 x y | trace ("f1: ") False = undefined
f1 x y = ... -- definition of f1

f2 x y | trace ("f2: ") False = undefined
f2 x y = ... -- definition of f2

-- same for f3

运行您的程序以查看调用了哪些函数。输出可能类似于

f3:
f2:
f1: 
<<loop>>

然后开始在跟踪函数中显示一些变量。例如,如果您将 f2 的跟踪更改为

f2 x y | trace ("f2: x: " ++ show x) False = undefined

那么输出将类似于:

f3:
f2: x: x_value
f1: 
<<loop>>

但是,如果您随后将 f2 的跟踪更改为

f2 x y | trace ("f2: x: " show x ++ " y: " ++ show y) False = undefined

然后输出将是

f3:
<<loop>>

因为由于循环依赖,无法评估 f2 的第二个参数。

所以你现在知道无限循环中的一个函数是 f2 并且它的第二个参数(但不是它的第一个参数)具有循环依赖关系。

调试愉快!

于 2012-08-30T06:52:00.533 回答
3

你不能使用 :back 和 :forward 来访问你的历史/跟踪并找出你的地图在调用之间的演变吗?

您应该能够发现导致递归循环的模式。

--如果它太棘手,您可能已经编写了一些太智能而无法调试的代码(或者可能太复杂,您应该重构它^^)--

于 2011-03-17T16:10:10.737 回答