如何在某些Python代码中跟踪Heisenbug?

时间:2010-09-28 10:28:34

标签: python debugging heisenbug

快速背景:我们有一个用Python编写的大型源代码库。它是域特定语言的编译器,内部所有内容都表示为有向图。这些有向图是从集合构建的,因此我们在Python中使用内置集类型。

问题是我们最初没有意识到Python主动使用set对象中缺少排序保证来使用更快的非确定性实现。因此,当您迭代集合中的对象(我们经常这样做)时,它们返回的顺序是弱随机的。它在每次执行时都不会改变,但它确实经常改变。从我所看到的调试代码开始,似乎源代码的哈希就像随机数生成器的种子一样。因此,即使在未执行的路径中更改代码,也会导致set迭代器更改生成元素的顺序。

我们的基本调试策略是将打印转储到我们认为错误的位置,根据输出对它们进行细化,直到找到错误为止。不是很优雅,但对于大多数事情来说它很有用。基本问题是添加/更改任何print语句会触发不同的行为和完全不同的执行路径。我们无法打印/记录所有内容,因为它会将编译器的执行时间从大约20秒(可管理)减慢到大约一小时(不是那么可管理)。

您如何诊断不经常发生的问题,并在您开始寻找时消失?

编辑以澄清: 几个答案提出了修复集合顺序的方法。正如torkildr在下面所说的那样“这里的问题不是设置表现得很奇怪,问题在于你的程序表现得好像没有”。这正是问题,但解决方案不是使用确定性集。这只会掩盖行为。问题是找到为什么我们的程序以这种方式运行并且修复这种行为。我们使用的算法应该在表示为无序集的图上工作。他们没有。我需要找出这些错误的位置以及它们发生的原因。

问题已解决: 事实证明,如果存储在集合中的对象的__eq____hash__方法之间的关系不是很大,那么我正在使用的Python实现(OS-X上的2.6)有效排序然后系统表现出所描述的弱随机行为。 set.add()的C实现中必须有一些代码,它们使用随机模块中的某些东西来构建表示。这会导致对系统熵池的依赖性,从而改变磁盘写入的顺序。

没有直接的答案,但阅读kriss的后续问题导致了解决这个问题的洞察力,所以他得到了投票。

3 个答案:

答案 0 :(得分:2)

为什么不在代码中改变任何影响设置输出排序的内容并使用pdb而不是添加打印?设置断点是否也会改变设置顺序?如果没有,pdb将允许您检查内部变量。

您对问题的描述也会带来一些谜团。你怎么发现有bug?如果可以在运行时进行此检测,则可能的策略是在您看到它(使用运行时ifs)后立即从您的代码运行pdb(如import pdb; pdb.set_trace()那么简单),而不是在更改后的后续执行中运行pdb代码。通过这种方式,您可以根本不需要更改代码来进行调试(但是可能会在稍后调试代码时强制删除这些断言)。

顺便说一句,你应该在编写代码时对所有代码进行单元测试,然后隐藏恶意错误的可能性就会小得多。

答案 1 :(得分:1)

我真的不明白为什么当你明白问题的根源是什么时,你会想要进一步研究这个问题。

她的主要问题是:你需要确定性的有序图; Python集不会给你这个。为什么不将实现更改为某些列表/集实现呢?

如果您在更改代码时说它发生了变化,那么当您认为设置算法可能已经过优化以使用它认为合适的最快存储模式时,这听起来并不令人惊讶。换句话说,您执行其他操作,存储变量,打印行以及更改内存。

编辑:总结一下;这里的问题不是集合表现得很奇怪,问题是你的程序表现得好像不行。

答案 2 :(得分:1)

Raymond Hettinger撰写了a recipe for ordered sets。所有方法的Big-O运行时间与集合相同。您可能想尝试将所有设置更改为订单。