背景
我有一个 C++ 扩展,它在缓冲区上运行 3D 分水岭。它有一个很好的 Cython 包装器来初始化一个巨大的signed char
s 缓冲区来表示体素。我在 python 中初始化一些本机数据结构(在编译的 cython 文件中),然后调用一个 C++ 函数来初始化缓冲区,另一个来实际运行算法(我也可以在 Cython 中编写这些,但我希望它也可以作为 C++ 库工作,而无需依赖 python.h。)
怪异
我正在调试我的代码,尝试不同的图像大小来衡量 RAM 使用率和速度等,我注意到结果有些奇怪——它们会根据我是否使用而改变python test.py
(特别/usr/bin/python
是在 Mac OS X 10.7 上) .5/Lion,它是 python 2.7) 或python
running import test
,并在其上调用一个函数(实际上,在我的笔记本电脑上(OS X 10.6.latest,使用 macports python 2.7)结果也确定性不同 - 每个平台/情况是不同的,但每个总是与自己相同。)。在所有情况下,都会调用相同的函数,从文件中加载一些输入数据,然后运行 C++ 模块。
关于 64 位 python 的注释 - 我没有使用 distutils 来编译此代码,而是类似于我在此处的答案(即通过显式-arch x86_64
调用)。这不应该意味着什么,我在 Activity Monitor 中的所有进程都被调用Intel (64-bit)
。
您可能知道,分水岭的重点是在像素汤中找到对象——在 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!
...
然而,
python
, import test
, test.run()
:
grain count: 1434
seemed to have 8000000 voxels, with average value 0.8398655
find cells:
running watershed algorithm...
found 927 cells from 1434 original grains!
...
这在交互式 python shell 和 中也是一样的bpython
,我最初认为这是罪魁祸首。
请注意,“平均值”数字是完全相同的——这表明最初在问题空间中标记了相同比例的体素——即,我的输入文件在(很可能)两次都以完全相同的方式初始化体素空间。
另请注意,算法的任何部分都不是不确定的;没有随机数或近似值;受到浮点错误(每次都应该相同)的影响,我们应该两次对完全相同的数字执行完全相同的计算。分水岭使用一个大的整数缓冲区(这里signed char
是 s)运行,结果是对这些整数的簇进行计数,所有这些都是在一个大的 C++ 调用中实现的。
我已经测试了__file__
相关模块对象的属性(它们本身就是导入的属性test
),它们指向安装watershed.so
在我系统的site-packages
.
问题
我什至不知道从哪里开始调试这个 - 如何使用相同的输入数据调用相同的函数并获得不同的结果?- 交互式 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
(标记的文件*
已在其他项目中广泛使用,并且经过了很好的测试,后缀irr
包含代码的文件仅在导致问题后才运行。)
细节
根据要求,主要节test/test.py
:
testfile = 'valid_filename'
if __name__ == "__main__":
# handles segfaults...
import faulthandler
faulthandler.enable()
run(testfile)
我的交互式调用看起来像:
import test
test.run(test.testfile)
线索
当我在直接解释器上运行它时:
import faulthandler
faulthandler.enable()
import test
test.run(test.testfile)
我从文件调用中得到结果(即 1242 个单元格),尽管当我在 bpython 中运行它时,它只是崩溃了。
这显然是问题的根源——向 Ignacio Vazquez-Abrams 致敬,因为他立即提出了正确的问题。
更新:
我在 faulthandler github 上打开了一个错误,我正在努力寻找解决方案。如果我发现人们可以从中学习的东西,我会将其发布为答案。