2

背景

我有一个 C++ 扩展,它在缓冲区上运行 3D 分水岭。它有一个很好的 Cython 包装器来初始化一个巨大的signed chars 缓冲区来表示体素。我在 python 中初始化一些本机数据结构(在编译的 cython 文件中),然后调用一个 C++ 函数来初始化缓冲区,另一个来实际运行算法(我也可以在 Cython 中编写这些,但我希望它也可以作为 C++ 库工作,而无需依赖 python.h。)

怪异

我正在调试我的代码,尝试不同的图像大小来衡量 RAM 使用率和速度等,我注意到结果有些奇怪——它们会根据我是否使用而改变python test.py(特别/usr/bin/python是在 Mac OS X 10.7 上) .5/Lion,它是 python 2.7) 或pythonrunning 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 上打开了一个错误,我正在努力寻找解决方案。如果我发现人们可以从中学习的东西,我会将其发布为答案。

4

1 回答 1

1

在广泛调试此应用程序后(printf()在运行期间的多个点输出所有数据,将输出管道输出到日志文件,diff对日志文件进行处理),我发现似乎是什么导致了奇怪的行为。

我在几个地方使用了未初始化的内存,并且(出于某种奇怪的原因)这给了我上面描述的两种情况之间可重复的行为差异——一种没有faulthandler,一种有。

顺便说一句,这也是为什么该错误从一台机器上消失但继续在另一台机器上表现出来的原因,在调试过程中(这真的应该给我一个线索!)

我在这里的错误是基于虚假的相关性来假设有关问题的事情-理论上,每次我访问垃圾内存时,垃圾内存都应该是不同的随机性(啊,理论上)。在这种情况下,我会更快地找到问题主要计算功能和橡皮鸭的打印输出。

所以,像往常一样,答案是错误不在库中,它在你的代码中 - 在这种情况下,这是我的malloc()一大块 RAM 的错,错误地假设我的代码的其他部分将要初始化它(他们只是有时这样做。)

于 2012-12-06T21:09:31.940 回答