11

速度を上げるために Python プログラムに C 拡張機能を書いていますが、3 次元の numpy 配列を渡そうとすると非常に奇妙な動作が発生します。2次元配列で動作しますが、3次元で動作させようとしているポインターで何かを台無しにしていると確信しています。しかし、ここに奇妙な部分があります。3 次元配列だけを渡すと、Bus Errorでクラッシュします。(Python で) 最初に変数を 2D 配列として作成し、次に 3D 配列で上書きすると、完全に機能します。変数が最初に空の配列で、次に 3D 配列である場合、Seg Faultでクラッシュします。どうしてそれが起こり得るのでしょうか?

また、3D 配列を機能させるのを手伝ってくれる人はいますか? それとも、あきらめて 2D 配列を渡し、自分で形状を変更する必要がありますか?

これが私のCコードです:

static PyObject* func(PyObject* self, PyObject* args) {
  PyObject *list2_obj;
  PyObject *list3_obj;
  if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj))
    return NULL;

  double **list2;
  double ***list3;

  //Create C arrays from numpy objects:
  int typenum = NPY_DOUBLE;
  PyArray_Descr *descr;
  descr = PyArray_DescrFromType(typenum);
  npy_intp dims[3];
  if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims, 3, descr) < 0) {
    PyErr_SetString(PyExc_TypeError, "error converting to c array");
    return NULL;
  }
  printf("2D: %f, 3D: %f.\n", list2[3][1], list3[1][0][2]);
}

上記の関数を呼び出す Python コードは次のとおりです。

import cmod, numpy
l2 = numpy.array([[1.0,2.0,3.0], [4.0,5.0,6.0], [7.0,8.0,9.0], [3.0, 5.0, 0.0]])

l3 = numpy.array([[2,7, 1], [6, 3, 9], [1, 10, 13], [4, 2, 6]])  # Line A
l3 = numpy.array([])                                             # Line B

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]],
                 [[1, 10, 13, 15], [4, 2, 6, 2]]])

cmod.func(l2, l3)

したがって、A 行と B 行の両方をコメントアウトすると、バス エラーでクラッシュします。行 A が存在し、行 B がコメントアウトされている場合、行 B はエラーなしで正しく実行されます。ライン B はあるが、ライン A がコメントアウトされている場合、正しい番号が出力されますが、Seg fault が表示されます。最後に、両方の行が存在する場合は、正しい番号と Seg faults も出力します。ここで一体何が起こっているのですか?

編集:わかりました。わお。だから私はintPythonで使用していましたがdouble、Cでそれらを呼び出していました.そして、それは1Dおよび2D配列でうまく機能していました. しかし、3D ではありません。そこで、l3 の Python 定義を float に変更したところ、すべてが素晴らしく機能するようになりました (Bi Rico に感謝します)。

しかし、今、ライン A & B でより奇妙な動作! 両方の行がコメントアウトされている場合、プログラムは機能します。行 B が存在するが、A がコメントアウトされている場合、それは機能し、両方がコメント解除されている場合も同様です。しかし、ライン A が存在し、ライン B がコメントアウトされている場合、素晴らしいバス エラーが再び発生します。私は将来これらを避けたいと思っています.Python変数の宣言がこの種の影響を与える理由を誰かが知っていますか?

編集 2:まあ、これらのエラーと同じくらい正気ではありませんが、それらはすべて、私が渡す 3 次元の numpy 配列によるものです。1 次元または 2 次元の配列のみを渡すと、期待どおりに動作し、他の Python 変数は何もしません。このことから、問題は Python の参照カウントのどこかにあると思われます。C コードでは、参照カウントが 3 次元配列に対して必要以上に減少し、その関数が返されると、Python はオブジェクトをクリーンアップしようとし、NULL ポインターを削除しようとします。これはあくまで私の推測であり、考えられるPy_INCREF();限りのことを試してみましたが、うまくいきませんでした。2D 配列を使用して C で再形成するだけだと思います。

4

4 に答える 4

3

これについてはすでにコメントで言及しましたが、少し洗い流すことでより明確になることを願っています。

C で numpy 配列を使用している場合、配列の型指定について明示することをお勧めします。具体的には、ポインターを として宣言しているように見えますが、Python コードでdouble ***list3作成l3している方法では、dtype を使用して配列を取得しますnpy_intp(私はそう思います)。これは、配列を作成するときに dtype を明示的に使用することで修正できます。

import cmod, numpy
l2 = numpy.array([[1.0,2.0,3.0],
                  [4.0,5.0,6.0],
                  [7.0,8.0,9.0],
                  [3.0, 5.0, 0.0]], dtype="double")

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]],
                  [[1, 10, 13, 15], [4, 2, 6, 2]]], dtype="double")

cmod.func(l2, l3)

別の注意点として、Python の動作方法により、「A 行目」と「B 行目」が C コードに何らかの影響を与えることはほぼ不可能です。これがあなたの経験と矛盾しているように思われることは承知していますが、この点については確信しています。

これについては少し確信が持てませんが、C での私の経験に基づくと、バス エラーとセグメンテーション フォールトは決定論的ではありません。それらは、メモリ割り当て、アライメント、およびアドレスに依存します。状況によっては、コードが 10 回正常に実行され、何も変更されていないにもかかわらず 11 回目の実行で失敗することがあります。

cython の使用を検討しましたか? 万人向けのオプションではないことはわかっていますが、オプションである場合は、typed memoryviewsを使用してほぼ C レベルのスピードアップを得ることができます。

于 2013-03-22T19:19:29.667 に答える
1

http://docs.scipy.org/doc/numpy/reference/c-api.array.html?highlight=pyarray_ascarray#PyArray_AsCArrayによると:

メモ C スタイルの配列のシミュレーションは、2 次元および 3 次元の配列では完全ではありません。たとえば、ポインターのシミュレートされた配列は、特定の静的に定義された 2 次元および 3 次元配列を期待するサブルーチンに渡すことはできません。この種の入力を必要とする関数に渡すには、必要な配列を静的に定義してデータをコピーする必要があります。

PyArray_AsCArrayこれは、C オーダーでデータを含むメモリのブロックを返すことを意味すると思います。ただし、そのデータにアクセスするには、より多くの情報が必要です ( http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Arrays,_dynamic_array_allocationを参照)。これは、次元を事前に把握し、配列を宣言してから、正しい順序でデータをコピーすることで実現できます。ただし、より一般的なケースの方が便利だと思います。ディメンションが返されるまで、ディメンションはわかりません。次のコードは、データをアドレス指定できるようにするために必要な C ポインター フレームワークを作成すると思います。

static PyObject* func(PyObject* self, PyObject* args) {
    PyObject *list2_obj;
    PyObject *list3_obj;
    if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj)) return NULL;

    double **list2;
    double ***list3;

    // For the final version
    double **final_array2;
    double **final_array2;

    // For loops
    int i,j;

    //Create C arrays from numpy objects:
    int typenum = NPY_DOUBLE;
    PyArray_Descr *descr;
    descr = PyArray_DescrFromType(typenum);

    // One per array coming back ...
    npy_intp dims2[2];
    npy_intp dims3[3];

    if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims2, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims3, 3, descr) < 0) {
        PyErr_SetString(PyExc_TypeError, "error converting to c array");
        return NULL;
    }

    // Create the pointer arrays needed to access the data

    // 2D array
    final_array2 = calloc(dim2[0], sizeof(double *));
    for (i=0; i<dim[0]; i++) final_array2[i] = list2 + dim2[1]*sizeof(double);

    // 2D array
    final_array3    = calloc(dim3[0], sizeof(double **));
    final_array3[0] = calloc(dim3[0]*dim3[1], sizeof(double *));
    for (i=0; i<dim[0]; i++) {
         final_array3[i] = list2 + dim3[1]*sizeof(double *);
         for (j=0; j<dim[1]; j++) {
             final_array[i][j] = final_array[i] + dim3[2]*sizeof(double);
         }
    }

    printf("2D: %f, 3D: %f.\n", final_array2[3][1], final_array3[1][0][2]);
    // Do stuff with the arrays

    // When ready to complete, free the array access stuff
    free(final_array2);

    free(final_array3[0]);
    free(final_array3);

    // I would guess you also need to free the stuff allocated by PyArray_AsCArray, if so:
    free(list2);
    free(list3);
}

の定義が見つかりませんでしnpy_intpた。上記は と同じであると想定していintます。そうでない場合は、コードを実行する前にdim2anddim3int配列に変換する必要があります。

于 2013-03-22T17:15:54.280 に答える