Python — мое расширение C ++ ведет себя по-разному с FaultHandler

Фон

У меня есть расширение C ++, которое запускает 3D-проход через буфер. У него есть хорошая оболочка Cython для инициализации массивного буфера signed chars для представления вокселей. Я инициализирую некоторые собственные структуры данных в python (в скомпилированном файле cython), а затем вызываю одну функцию C ++ для инициализации буфера, а другую — для фактического запуска алгоритма (я мог бы написать их и в Cython, но я бы хотел, чтобы работать как библиотека C ++ без зависимости от python.h.)

странность

Я нахожусь в процессе отладки своего кода, пробую разные размеры изображений, чтобы измерить использование ОЗУ и скорость, и т. Д., И я заметил кое-что очень странное в результатах — они меняются в зависимости от того, использую ли я python test.py (в частности, /usr/bin/python в Mac OS X 10.7.5 / Lion, который является Python 2.7) или python и работает import testи вызов функции на нем (и действительно, на моем ноутбуке (OS X 10.6.latest, с macports python 2.7) результаты также детерминированно различны — каждая платформа / ситуация различна, но каждая всегда совпадает с собой. ). Во всех случаях вызывается одна и та же функция, загружается часть входных данных из файла и запускается модуль C ++.

Замечание о 64-битном питоне — я не использую 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 и bpython, который я изначально думал, был виноват.

Обратите внимание, что число «среднего значения» точно такое же — это указывает на то, что изначально была отмечена та же доля вокселей, что и в проблемном пространстве — то есть, что мой входной файл был инициализирован (очень, очень вероятно) точно так же, как оба раза в воксели-пространство.

Также обратите внимание, что ни одна часть алгоритма не является недетерминированной; нет случайных чисел или приближений; При условии ошибки с плавающей запятой (которая должна быть одинаковой каждый раз), мы должны выполнять одни и те же вычисления с одинаковыми числами оба раза. Водораздел проходит с использованием большого буфера целых чисел (здесь signed chars) и результаты подсчитывают кластеры этих целых чисел, все из которых реализованы в одном большом вызове C ++.

Я проверил __file__ атрибут соответствующих объектов модуля (которые сами являются атрибутами импортируемого test), и они указывают на то же самое установленное watershed.so в моей системе site-packages,

Вопросы

Я даже не знаю, с чего начать отладку — как можно вызвать одну и ту же функцию с одними и теми же входными данными и получить разные результаты? — а что с интерактивным питоном может вызвать это (например, путем изменения способа инициализации данных)? — Какие части (довольно большой) кодовой базы имеют отношение к этим вопросам?

По моему опыту гораздо полезнее опубликовать ВСЕ код в вопросе 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, он просто падает.

Это явно источник проблемы — снимаю шляпу перед Игнасио Васкесом-Абрамсом, который сразу же задает правильный вопрос.

ОБНОВИТЬ:

Я открыл баг на неисправном обработчике github и я работаю над решением проблемы. Если я найду что-то, чему люди могут научиться, я опубликую это как ответ.

2

Решение

После отладки этого приложения (printf()выводя все данные в нескольких точках во время выполнения, передавая выходные данные в файлы журнала, diffя нашел то, что, по-видимому, вызывало странное поведение.

Я использовал неинициализированную память в нескольких местах, и (по какой-то странной причине) это дало мне повторяющиеся различия в поведении между двумя описанными выше случаями — один без faulthandler и один с.

Кстати, это также, почему ошибка исчез с одной машины, но продолжал проявлять себя на другой, частично через отладку (что действительно должно было дать мне подсказку!)

Моя ошибка здесь заключалась в том, чтобы предположить, что проблема связана с ложной корреляцией — теоретически баран мусора должен был быть по-разному случайным каждый раз, когда я к нему обращался (аа, теория.) В этом случае я бы быстрее нашел проблему с распечатка основной функции расчета и резиновая утка.

Итак, как обычно, ответ ошибка не в библиотеке, она где-то в вашем коде — в этом случае это была моя вина за malloc()Я использовал ложную часть оперативной памяти, ложно предполагая, что другие части моего кода собирались его инициализировать (что они делали только иногда).

1

Другие решения

Других решений пока нет …