2

バックグラウンド

バッファ上で3D流域パスを実行するC++拡張機能があります。signed charボクセルを表すためにsの大規模なバッファを初期化するための優れたCythonラッパーがあります。Pythonでいくつかのネイティブデータ構造を(コンパイルされたcythonファイルで)初期化し、次に1つのC ++関数を呼び出してバッファーを初期化し、別の関数を呼び出して実際にアルゴリズムを実行します(Cythonでこれらを記述した可能性もありますが、 python.hに依存せずにC++ライブラリとしても機能します。)

奇妙さ

私はコードをデバッグしている最中です。RAMの使用量や速度などを測定するためにさまざまな画像サイズを試していますが、結果について非常に奇妙なことに気づきました。使用するかどうかによって異なりますpython test.py(特に/usr/bin/pythonMac OS X 10.7の場合)。 .5 / Lion、これはpython 2.7)またはpython実行していてimport test、その上で関数を呼び出します(実際、私のラップトップ(OS X 10.6.latest、macports python 2.7を使用)では、結果も決定論的に異なります-各プラットフォーム/状況は異なりますが、それぞれが常にそれ自体と同じです。)いずれの場合も、同じ関数が呼び出され、ファイルからいくつかの入力データが読み込まれ、C++モジュールが実行されます。

64ビットPythonに関する注意-私はこのコードをコンパイルするためにdistutilsを使用していませんが、ここでの私の答えに似ています(つまり、明示的な-arch x86_64呼び出しを使用しています)。これは何の意味もありません。ActivityMonitorのすべてのプロセスはと呼ばれ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!
...

これは、bpython私が最初に非難したと思っていたインタラクティブなpythonシェルとと同じです。

「平均値」の数値はまったく同じであることに注意してください。これは、ボクセルの同じ割合が最初に問題空間でマークされたことを示します。つまり、入力ファイルが(おそらく)まったく同じ方法で両方の時間に初期化されたことを示します。ボクセルスペース。

また、アルゴリズムのどの部分も非決定論的ではないことに注意してください。乱数や近似値はありません。浮動小数点エラー(毎回同じである必要があります)が発生する可能性がありますが、両方の時間でまったく同じ数値に対してまったく同じ計算を実行する必要があります。Watershedは、整数の大きなバッファー(ここではsigned chars)を使用して実行され、結果はそれらの整数のクラスターをカウントします。これらはすべて、1つの大きなC++呼び出しで実装されます。

__file__関連するモジュールオブジェクトの属性(それ自体はインポートされたの属性です)をテストしましたが、それらはシステムにtestインストールされているものと同じものを指しています。watershed.sosite-packages

質問

これのデバッグをどこから始めればよいのかさえわかりません。同じ入力データで同じ関数を呼び出して、異なる結果を得るにはどうすればよいでしょうか。-インタラクティブなPythonはこれを引き起こす可能性があります(たとえば、データの初期化方法を変更することによって)?-(かなり大きな)コードベースのどの部分がこれらの質問に関連していますか?

私の経験では、すべてのコードをスタックオーバーフローの質問に投稿する方がはるかに便利であり、問​​題がどこにあるかを知っているとは限りません。ただし、ここでは数千行のコードであり、文字通りどこから始めればよいのかわかりません。リクエストに応じて小さなスニペットを投稿できてうれしいです。

また、デバッグ戦略(チェックできるインタープリターの状態、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で実行すると、クラッシュするだけです。

これが明らかに問題の原因です。すぐに正しい質問をしてくれたIgnacioVazquez-Abramsに敬意を表します。

アップデート:

私はfaulthandlergithubでバグを開き、解決に向けて取り組んでいます。人々が学ぶことができる何かを見つけたら、私はそれを答えとして投稿します。

4

1 に答える 1

1

このアプリケーションを広範囲にデバッグした後 (printf()実行中の複数のポイントですべてのデータを取り出し、出力をログ ファイルにパイプしdiff、ログ ファイルを処理する)、奇妙な動作の原因と思われるものを見つけました。

いくつかの場所で初期化されていないメモリを使用していましたが、(いくつかの奇妙な理由で) これにより、上記で説明した 2 つのケース (ない場合とある場合) の間で再現性のある動作の違いが生じましたfaulthandler

ちなみに、これがバグがあるマシンから消えたのに、デバッグの途中で別のマシンで現れ続けた理由でもあります (これは本当に手がかりを与えてくれるはずでした!)

ここでの私の間違いは、疑似相関に基づいて問題について推測することでした。理論的には、ガベージ RAM は、アクセスするたびにランダムに異なるはずです (ああ、理論です)。主な計算関数とラバーダックのプリントアウト。

したがって、いつものように、答えはバグがライブラリにあるのではなく、コードのどこかにあるということです。この場合、malloc()コードの他の部分が初期化されると誤って想定して、RAM のチャンクを ing したのは私のせいでした。それ(彼らは時々しかしませんでした。)

于 2012-12-06T21:09:31.940 に答える