C++ 我的c++;扩展与faulthandler的行为不同

C++ 我的c++;扩展与faulthandler的行为不同,c++,python,cython,C++,Python,Cython,背景 我有一个C++扩展,它在缓冲区上运行一个3D分水岭。它有一个很好的Cython包装器,可以初始化大量的signed chars缓冲区来表示体素。我在Python中初始化一些本地数据结构(在一个编译的Cython文件中),然后调用一个C++函数初始化缓冲区,另一个实际运行算法(我也可以在Cython写这些,但是我希望它也能作为一个C++库,而不用Python .h依赖)。 古怪 我正在调试我的代码,尝试使用不同的图像大小来衡量RAM的使用情况和速度等,我注意到结果中有一些非常奇怪的地方——它

背景

我有一个C++扩展,它在缓冲区上运行一个3D分水岭。它有一个很好的Cython包装器,可以初始化大量的

signed char
s缓冲区来表示体素。我在Python中初始化一些本地数据结构(在一个编译的Cython文件中),然后调用一个C++函数初始化缓冲区,另一个实际运行算法(我也可以在Cython写这些,但是我希望它也能作为一个C++库,而不用Python .h依赖)。 古怪

我正在调试我的代码,尝试使用不同的图像大小来衡量RAM的使用情况和速度等,我注意到结果中有一些非常奇怪的地方——它们的变化取决于我是否使用
python test.py
(特别是在Mac OS X 10.7.5/Lion上的
/usr/bin/python
,即python 2.7)或者
python
运行
import test
,并在上面调用一个函数(事实上,在我的笔记本电脑上(OS X 10.6.latest,macports python 2.7),结果也完全不同——每个平台/情况都不同,但每个平台/情况都是一样的。)。在所有情况下,调用相同的函数,从文件加载一些输入数据,并运行C++模块。 关于64位python的说明-我不是使用distutils编译此代码,而是类似于我的答案的东西(即,使用显式的
-arch x86_64
调用)。这并不意味着什么,活动监视器中的所有进程都被称为
Intel(64位)

正如你可能知道的,分水岭的意义在于在像素汤中找到对象——在2D中,它通常用于照片。在这里,我用它来寻找3D中的团块,方法大致相同——我从图像中的一些团块(“颗粒”)开始,我想在它们之间的空间中找到反向团块(“细胞”)

结果的变化方式是,我确实发现了不同数量的肿块。对于完全相同的输入数据:

python test.py

grain count: 1434
seemed to have 8000000 voxels, with average value 0.8398655
find cells:
running watershed algorithm...
found 1242 cells from 1434 original grains!
...
testfile = 'valid_filename'

if __name__ == "__main__":
  # handles segfaults...
  import faulthandler
  faulthandler.enable()
  run(testfile)
但是,

python
import test
test.run()

这在交互式python shell和
bpython
中也是一样的,我最初认为这是罪魁祸首

请注意,“平均值”数字完全相同-这表明同一部分体素最初被标记为在问题空间中-即,我的输入文件在体素空间中以(非常可能)完全相同的方式初始化两次

还要注意,算法的任何部分都不是不确定的;没有随机数或近似值;根据浮点错误(每次都应该相同),我们应该在两次都完全相同的数字上执行完全相同的计算。分水岭使用整数的大缓冲区(这里是代码>符号char < /Calp> s),结果是对这些整数的簇进行计数,所有这些都是在一个大的C++调用中实现的。 我已经测试了相关模块对象的
\uuu文件
属性(它们本身就是导入的
测试
的属性),它们指向同一个已安装的
分水岭。因此,在我的系统的
站点包中

问题

我甚至不知道从哪里开始调试这个-如何用相同的输入数据调用相同的函数并得到不同的结果交互式python可能会导致这种情况(例如,通过更改数据初始化的方式)怎么样代码库的哪些部分(相当大)与这些问题相关

根据我的经验,在stackoverflow问题中发布所有代码更有用,而不是假设您知道问题所在。然而,这里有数千行代码,我真的不知道从哪里开始!我很乐意应要求发布一些小片段

<>我也很高兴听到调试策略-解释状态,我可以检查,Python可能影响导入的C++二进制文件的细节,等等。 以下是代码的结构:

project/
  clibs/
    custom_types/
      adjacency.cpp (and hpp)     << graph adjacency (2nd pass; irrelevant = irr)
     *array.c (and h)             << dynamic array of void*s
     *bit_vector.c (and h)        << int* as bitfield with helper functions
      polyhedron.cpp (and hpp)    << for voxel initialisation; convex hull result
      smallest_ints.cpp (and hpp) << for voxel entity affiliation tracking (irr)
    custom_types.cpp (and hpp)    << wraps all files in custom_types/
    delaunay.cpp (and hpp)        << marshals calls to stripack.f90
   *stripack.f90 (and h)          << for computing the convex hulls of grains
    tensors/
     *D3Vector.cpp (and hpp)      << 3D double vector impl with operators
    watershed.cpp (and hpp)       << main algorithm entry points (ini, +two passes)
  pywat/
    __init__.py
    watershed.pyx                 << cython class, python entry points.
    geometric_graph.py            << python code for post processing (irr)
  setup.py                        << compile and install directives
  test/
    test.py                       << entry point for testing installed lib
我的交互调用如下所示:

import test
test.run(test.testfile)
线索

当我在straight解释器上运行此命令时:

import faulthandler
faulthandler.enable()
import test
test.run(test.testfile)
我从文件调用(即1242个单元格)中获得结果,尽管当我在bpython中运行它时,它只是崩溃了

这显然是问题的根源——向伊格纳西奥·巴斯克斯·艾布拉姆斯致敬,因为他立刻提出了正确的问题

更新:


我一直在努力寻找解决办法。如果我发现人们可以从中学习的东西,我会把它作为一个答案发布。

在对这个应用程序进行广泛调试后(
printf()
在运行过程中在多个点输出所有数据,将输出传输到日志文件,
diff
ing日志文件),我发现了导致这种奇怪行为的原因

我在几个地方使用了未初始化的内存,并且(出于某种奇怪的原因)这让我在我上面描述的两种情况之间产生了可重复的行为差异——一种没有
故障处理程序,另一种有

顺便说一句,这也是为什么这个bug从一台机器上消失了,但在另一台机器上继续出现,部分是通过调试(这真的应该给我一个线索!)

我在这里的错误是基于一个虚假的相关性来假设问题的一些东西——理论上,每次我访问垃圾ram时,垃圾ram都应该是不同的随机性(啊,理论上)。在这种情况下,我可以通过主计算函数的打印输出和

所以,像往常一样,答案是这个bug不在库中,它在代码中的某个地方——在这种情况下,
malloc()
对一块RAM进行初始化是我的错,错误地认为我的代码的其他部分将初始化它(他们只是偶尔这样做)
import faulthandler
faulthandler.enable()
import test
test.run(test.testfile)