web-dev-qa-db-ja.com

Cythonタイプのmemoryviews:それらは実際には何ですか?

Cython documentation は、それらが何を許可するか、それらを宣言する方法、およびそれらを使用する方法を非常によく説明しています。

しかし、それが実際に何であるかはまだ私にはわかりません。たとえば、次のようなnumpy配列からの単純な割り当て:

my_arr = np.empty(10, np.int32)
cdef int [:] new_arr = my_arr

my_arrへのアクセス/割り当てを高速化できます。

それは舞台裏で何が起こっているのですか? Numpyはすでにメモリ内の要素を連続して割り当てる必要があるので、memoryviewsはどうなりますか?明らかにそれほど多くはありませんが、実際には、numpy配列new_arrのmemoryview割り当ては同等である必要があります

cdef np.ndarray[np.int32_t, ndim=1] new_arr = np.empty(10, np.int32)

速度の面で。ただし、me​​moryviewsはnumpy配列バッファよりも一般的であると見なされます。追加された「一般化」が重要/興味深いという簡単な例を作成できますか?

さらに、物事をできるだけ速くするためにすでにポインタを割り当てている場合、それを型付きメモリビューにキャストすることの利点は何ですか? (この質問への答えは上記のものと同じかもしれません)

cdef int *my_arr = <int *> malloc(N * sizeof(int))
cdef int[:] new_arr = <int[:N]>my_arr
17
Gioker

メモリビューとは:

関数を書くとき:

cdef double[:] a

最終的には__Pyx_memviewsliceオブジェクトになります。

typedef struct {
  struct __pyx_memoryview_obj *memview;
  char *data;
  Py_ssize_t shape[8];
  Py_ssize_t strides[8];
  Py_ssize_t suboffsets[8];
} __Pyx_memviewslice;

Memoryviewには、(通常は)直接所有していないデータのCポインタが含まれています。また、基になるPythonオブジェクト(struct __pyx_memoryview_obj *memview;)へのポインターも含まれています。データがPythonオブジェクトによって所有されている場合は、memviewはそれへの参照を保持し、データを保持するPythonオブジェクトは、memoryviewが存在する限り存続することを保証します。

生データへのポインターとそのインデックス付け方法の情報(shapestrides、およびsuboffsets)の組み合わせにより、Cythonは生データポインターを使用してインデックス付けを行うことができます。そしていくつかの簡単なC数学(これは非常に効率的です)。例えば。:

x=a[0]

次のようなものを与えます:

(*((double *) ( /* dim=0 */ (__pyx_v_a.data + __pyx_t_2 * __pyx_v_a.strides[0]) )));

対照的に、型指定されていないオブジェクトを操作して、次のように記述した場合:

a = np.array([1,2,3]) # note no typedef
x = x[0]

インデックス作成は次のように行われます。

__Pyx_GetItemInt(__pyx_v_a, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1);

これ自体が一連のPython C-api呼び出し(非常に遅い)に拡張されます。最終的にはa__getitem__メソッドを呼び出します。


型付きのnumpy配列との比較:実際には大きな違いはありません。次のようなことをした場合:

cdef np.ndarray[np.int32_t, ndim=1] new_arr

それは実際にはメモリビューのように機能し、生のポインタにアクセスでき、速度は非常に似ているはずです。

Memoryviewsを使用する利点は、より広い範囲の配列型( 標準ライブラリ配列 など)を使用できるため、関数を呼び出すことができる型についてより柔軟になることです。これは、一般的なPython「ダックタイピング」の考え方-コードは(型をチェックするのではなく)正しい方法で動作するすべてのパラメーターで機能する必要があるという考えに適合します。

2番目の(小さな)利点は、モジュールを構築するためにnumpyヘッダーが必要ないことです。

3番目の(おそらくより大きな)利点は、メモリビューをGILなしで初期化できるのに対し、cdef np.ndarraysは初期化できないことです( http://docs.cython.org/src/userguide/memoryviews.html#comparison- to-the-old-buffer-support

Memoryviewのわずかな欠点は、セットアップが少し遅いように見えることです。


malloced intポインタを使用する場合と比較して:

速度の利点は得られません(ただし、速度の低下が大きすぎることもありません)。 memoryviewを使用して変換することの小さな利点は次のとおりです。

  1. Pythonから、またはCythonの内部で使用できる関数を記述できます。

    cpdef do_something_useful(double[:] x):
        # can be called from Python with any array type or from Cython
        # with something that's already a memoryview
        ....
    
  2. Cythonにこのタイプの配列のメモリの解放を処理させることができます。これにより、寿命が不明なものの寿命が単純化される可能性があります。 http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays 、特に.callback_free_dataを参照してください。

  3. データをpython pythonコードに戻すことができます(基になる__pyx_memoryview_objなどを取得します)。メモリには十分注意してください。ここでの管理(つまり、ポイント2を参照してください!)。

  4. 他にできることは、ポインターへのポインターとして定義された2D配列のようなものを処理することです(例:double**)。 http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts を参照してください。私は一般的にこのタイプの配列が好きではありませんが、すでにifを使用している既存のCコードがある場合は、それとインターフェイスできます(そして、それをPythonなので、Pythonコードでも使用できます)。

25
DavidW