簡體   English   中英

如何通過cython將numpy數組列表傳遞給C ++

[英]how to pass list of numpy arrays to c++ via cython

我想將2d numpy數組的列表傳遞給c ++函數。 我的第一個想法是使用std::vector<float *>接收數組列表,但是我找不到傳遞列表的方法。

c ++函數如下所示:

double cpp_func(const std::vector<const float*>& vec) {
    return 0.0;
}

Cython函數如下所示:

cpdef py_func(list list_of_array):
    cdef vector[float*] vec
    cdef size_t i
    cdef size_t n = len(list_of_array)
    for i in range(n):
        vec.push_back(&list_of_array[i][0][0])  # error: Cannot take address of Python object
    return cpp_func(vec)

我曾嘗試使用list[float[:,:]]聲明list_of_array ,但也無法正常工作。

我將稍微更改您的函數的簽名:

  • 對於每個numpy數組,函數還需要知道此數組中的元素數
  • 數據是double *而不是float *因為這對應於默認的np.float -type。 但這可以根據您的需要進行調整。

這導致以下c ++接口/代碼(為方便起見,我將Cy - thon> = 0.28使用C-verbatim-code功能):

%%cython --cplus -c=-std=c++11
from libcpp.vector cimport vector
cdef extern from *:
    """
    struct Numpy1DArray{
        double *ptr;
        int   size;
    };

    static double cpp_func(const std::vector<Numpy1DArray> &vec){
          // Fill with life to see, that it really works:
          double res = 0.0;
          for(const auto &a : vec){
              if(a.size>0)
                res+=a.ptr[0];
          }
          return res;
    }   
    """
    cdef struct Numpy1DArray:
        double *ptr
        int size          
    double cpp_func(const vector[Numpy1DArray] &vec)
    ...

struct Numpy1DArray只是捆綁了一個np數組所需的信息,因為這不僅僅是指向連續數據的指針。


天真的版本

現在,編寫包裝函數非常簡單:

%%cython --cplus -c=-std=c++11
....
def call_cpp_func(list_of_arrays):
  cdef Numpy1DArray ar_descr
  cdef vector[Numpy1DArray] vec
  cdef double[::1] ar
  for ar in list_of_arrays:  # coerse elements to double[::1]
        ar_descr.size = ar.size
        if ar.size > 0:
            ar_descr.ptr = &ar[0]
        else:
            ar_descr.ptr = NULL  # set to nullptr
        vec.push_back(ar_descr)

  return cpp_func(vec)

有一些值得注意的事情:

  • 您需要將list的元素強制轉換為實現緩沖區協議的內容,否則&ar[0]顯然將不起作用,因為Cython希望ar[0]是Python對象。 順便說一句,這就是您所錯過的。
  • 我選擇了Cython的內存視圖(即double[::1] )作為強制目標。 np.ndarray相比,優點在於它還可以與array.array一起array.array ,並且還可以自動檢查數據是否連續(即::1的含義)。
  • 一個常見的陷阱是訪問ar[0]以獲得空的ndarray此訪問必須受到保護。
  • 此代碼不是線程安全的。 另一個線程可能使指針無效,例如,通過就地調整numpy數組的大小或完全刪除numpy數組。
  • IIRC,對於Python 2,您將必須cimport array以便使代碼與array.array一起array.array

最后,這是一個測試代碼是否有效的測試(列表中還有一個array.array可以說明這一點):

import array
import numpy as np
lst = (np.full(3, 1.0), np.full(0, 2.0), array.array('d', [2.0]))
call_cpp_func(lst)  # 3.0 as expected!

線程安全版本

上面的代碼也可以用線程安全的方式編寫。 可能的問題是:

  1. 另一個線程可以通過調用例如list_of_arrays.clear()觸發numpy-array的刪除-之后,周圍將不再有數組的引用,它們將被刪除。 這意味着只要使用指針,就需要保留對每個輸入數組的引用。
  2. 另一個線程可以調整數組的大小,從而使指針無效。 這意味着我們必須使用緩沖區協議-它的__getbuffer__鎖定緩沖區,因此一旦完成計算,就不能使它無效並通過__releasebuffer__釋放緩沖區。

Cython的內存視圖可用於鎖定緩沖區並保持輸入數組周圍的引用:

%%cython --cplus -c=-std=c++11
....
def call_cpp_func_safe(list_of_arrays):
     cdef Numpy1DArray ar_descr
     cdef vector[Numpy1DArray] vec
     cdef double[::1] ar
     cdef list stay_alive = []
     for ar in list_of_arrays:  # coerse elements to double[::1]
            stay_alive.append(ar)    # keep arrays alive and locked
            ar_descr.size = ar.size
            if ar.size > 0:
                ar_descr.ptr = &ar[0]
            else:
                ar_descr.ptr = NULL  # set to nullptr
            vec.push_back(ar_descr)
     return cpp_func(vec)

開銷很小:將內存視圖添加到列表中-安全性的代價。


釋放吉爾

最后一項改進:可以在計算cpp_fun時釋放gil,這意味着我們必須將cpp_func導入為nogil並釋放它,為什么調用該函數:

%%cython --cplus -c=-std=c++11
from libcpp.vector cimport vector
cdef extern from *:
    ....          
    double cpp_func(const vector[Numpy1DArray] &vec) nogil
...

def call_cpp_func(list_of_arrays):
...
    with nogil:
        result = cpp_func(vec)       
    return result

Cython會發現, result是double類型的,因此可以在調用cpp_func同時釋放gil。

暫無
暫無

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

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