簡體   English   中英

將三維numpy數組傳遞給C.

[英]Passing 3-dimensional numpy array to C

我正在為我的Python程序編寫一個C擴展用於速度目的,並且遇到一些非常奇怪的行為,試圖傳入一個三維的numpy數組。 它適用於一個二維數組,但我確定我正在搞砸一些指針試圖讓它與第三維一起工作。 但這是奇怪的部分。 如果我只是傳入一個三維數組,它會因總線錯誤而崩潰。 如果(在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在那里但是線路A被注釋掉,它會輸出正確的數字但是會出現Seg故障。 最后,如果兩條線都存在,它還會打印正確的數字,然后是Seg故障。 到底是怎么回事?

編輯:好的。 哇。 所以我在Python中使用int但在C中調用它們是double的。這對於1D和2D數組來說效果很好。 但不是3D。 因此我將l3的Python定義更改為浮點數,現在這一切都非常有效( 非常感謝Bi Rico )。

但現在,線A和B更奇怪的行為! 現在如果兩行都被注釋掉,程序就可以運行了。 如果存在B行但是A被注釋掉,則它可以工作,如果兩個都被取消注釋,則同上。 但是如果存在A行並且B被注釋掉,我再次得到了那個夢幻般的總線錯誤。 我真的想在將來避免這些,所以有沒有人知道為什么Python變量的聲明會產生這種影響?

編輯2:嗯,像這些錯誤一樣瘋狂,它們都歸因於我傳入的三維numpy數組。如果我只傳入1或2-D數組,它的行為與預期的一樣,並且操縱其他Python變量什么都不做。 這讓我相信問題出在Python的引用計數中。 在C代碼中,引用計數比3-D數組的數量減少得多,當該函數返回時,Python嘗試清理對象,並嘗試刪除NULL指針。 這只是我的猜測,我試過Py_INCREF(); 我能想到的一切都無濟於事。 我想我只會使用2D數組並在C中重塑它。

我通常使用PyArray_GETPTR直接訪問numpy數組元素,而不是轉換為c風格的數組(請參閱http://docs.scipy.org/doc/numpy/reference/c-api.array.html#data-access )。

例如,要訪問double類型的三維numpy數組的元素,請使用double elem=*((double *)PyArray_GETPTR3(list3_obj,i,j,k))

對於您的應用程序,您可以使用PyArray_NDIM檢測每個數組的正確維數,然后使用相應版本的PyArray_GETPTR訪問元素。

我已經在評論中提到過這一點,但我希望將其簡化一下有助於使其更加清晰。

當您在C中使用numpy數組時,最好明確指出數組的類型。 具體來說,看起來你將你的指針聲明為double ***list3 ,但是你在python代碼中創建l3方式你會得到一個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嗎? 我知道這不是每個人的選擇,但如果它是一個選項,你可以使用類型化的內存視圖獲得近乎C級的加速。

根據http://docs.scipy.org/doc/numpy/reference/c-api.array.html?highlight=pyarray_ascarray#PyArray_AsCArray

注意對於2維和3維陣列,C樣式陣列的模擬不完整。 例如,模擬的指針數組不能傳遞給期望特定的,靜態定義的2-d和3-d數組的子程序。 要傳遞給需要這些輸入的函數,必須靜態定義所需的數組和復制數據。

我認為這意味着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相同。 如果不是,則需要在執行代碼之前將dim2dim3轉換為int數組。

numpy C-API中存在一個錯誤,現在應該修復:

https://github.com/numpy/numpy/pull/5314

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM