背景
我有一个C++扩展,可以在缓冲区上运行3D分水岭。它有一个不错的Cython包装器,用于初始化大量signed char
的缓冲区以表示体素。我在python中初始化了一些本机数据结构(在已编译的cython文件中),然后调用一个C++函数来初始化缓冲区,然后调用另一个C++函数来实际运行算法(我也可以在Cython中编写这些函数,但是我想用无需python.h依赖也可以用作C++库。)
古怪的
我正在调试我的代码,尝试使用不同的图像大小来衡量RAM使用情况和速度等,并且我注意到结果有些奇怪-它们的变化取决于我是否使用python test.py
(特别是在Mac OS上为/usr/bin/python
) X 10.7.5 / Lion,即python 2.7)或python
并运行import test
,并在其上调用一个函数(实际上,在我的笔记本电脑上(使用Macports python 2.7的OS X 10.6.latest),结果在确定性上也有所不同-每个平台/位置都不同,但每个平台/位置始终相同。)在所有情况下,都将调用相同的函数,从文件中加载一些输入数据,然后运行C++模块。
关于64位python的注释-我没有使用distutils来编译此代码,而是类似于我的答案here(即使用显式的-arch x86_64
调用)。这并不意味着什么,我在 Activity 监视器中的所有进程都称为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
)运行,结果是计算这些整数的簇,所有这些都在一个大型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)立即提出正确的问题。
更新:
我有opened a bug on the faulthandler github,我正在努力寻求解决方案。如果我发现人们可以从中学到的东西,我会将其发布为答案。
最佳答案
广泛调试该应用程序之后(在运行过程中printf()
在所有点上提取所有数据,将输出管道传输到日志文件,diff
在日志文件中),我发现似乎引起异常行为的原因。
我在几个地方使用了未初始化的内存,并且(出于某些奇怪的原因),这使我在上述两种情况之间可重复的行为差异–一种是没有faulthandler
的情况,另一种是。
顺便说一句,这也是为什么该错误从一台机器上消失了,但在调试过程中继续在另一台机器上显现出来的原因(这确实应该给我一个提示!)
我在这里的错误是基于虚假相关性来假设有关问题的事情-理论上,每次访问垃圾ram的随机内存都应该是不同的(理论上是啊)。在这种情况下,我会更快地用主要计算功能的打印输出和一个rubber duck。
因此,像往常一样,答案是该错误不在库中,而是在您的代码中-在这种情况下,malloc()
占用了一块RAM是我的错,错误地认为我的代码的其他部分要初始化它(他们有时只是这样做)。
关于c++ - 我的c++扩展程序与Faulthandler的行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13711463/