10

Cython を使用して、中間多次元配列の生成を伴う高価な操作を並列化しようとしています。

次の非常に単純化されたコードは、私がやろうとしていることの種類を示しています。

import numpy as np
cimport cython
cimport numpy as np
from cython.parallel cimport prange
from libc.stdlib cimport malloc, free


@cython.boundscheck(False)
@cython.wraparound(False)
def embarrasingly_parallel_example(char[:, :] A):

    cdef unsigned int m = A.shape[0]
    cdef unsigned int n = A.shape[1]
    cdef np.ndarray[np.float64_t, ndim = 2] out = np.empty((m, m), np.float64)
    cdef unsigned int ii, jj
    cdef double[:, :] tmp

    for ii in prange(m, nogil=True):
        for jj in range(m):

            # allocate a temporary array to hold the result of
            # expensive_function_1
            tmp_carray = <double * > malloc((n ** 2) * sizeof(double))

            # a 2D typed memoryview onto tmp_carray
            tmp = <double[:n, :n] > tmp_carray

            # shove the intermediate result in tmp
            expensive_function_1(A[ii, :], A[jj, :], tmp)

            # get the final (scalar) output for this ii, jj
            out[ii, jj] = expensive_function_2(tmp)

            # free the intermediate array
            free(tmp_carray)

    return out


# some silly examples - the actual operation I'm performing is a lot more
# involved
# ------------------------------------------------------------------------
@cython.boundscheck(False)
@cython.wraparound(False)
cdef void expensive_function_1(char[:] x, char[:] y, double[:, :] tmp):

    cdef unsigned int m = tmp.shape[0]
    cdef unsigned int n = x.shape[0]
    cdef unsigned int ii, jj

    for ii in range(m):
        for jj in range(m):
            tmp[ii, jj] = 0
            for kk in range(n):
                tmp[ii, jj] += (x[kk] + y[kk]) * (ii - jj)


@cython.boundscheck(False)
@cython.wraparound(False)
cdef double expensive_function_2(double[:, :] tmp):

    cdef unsigned int m = tmp.shape[0]
    cdef unsigned int ii, jj
    cdef double result = 0

    for ii in range(m):
        for jj in range(m):
            result += tmp[ii, jj]

    return result

これがコンパイルに失敗する理由は少なくとも 2 つあります。

  1. の出力に基づいてcython -a、型付きメモリ ビューを作成します。

    cdef double[:, :] tmp = <double[:n, :n] > tmp_carray
    

    Python API 呼び出しが含まれているように思われるため、GIL を解放して外側のループを並行して実行することはできません。

    型付きメモリ ビューは Python オブジェクトではないので、子プロセスは最初に GIL を取得しなくても作成できるはずだという印象を受けました。これは事実ですか?

2.prange(m, nogil=True)通常の に置き換えても、Cythonは内部ループ内range(m)に a が存在することを好まないようです。cdef

    Error compiling Cython file:
    ------------------------------------------------------------
    ...
                # allocate a temporary array to hold the result of
                # expensive_function_1
                tmp_carray = <double*> malloc((n ** 2) * sizeof(double))

                # a 2D typed memoryview onto tmp_carray
                cdef double[:, :] tmp = <double[:n, :n]> tmp_carray
                    ^
    ------------------------------------------------------------

    parallel_allocate.pyx:26:17: cdef statement not allowed here

アップデート

2番目の問題は、移動することで簡単に解決されたことが判明しました

 cdef double[:, :] tmp

forループの外で、ただ代入する

 tmp = <double[:n, :n] > tmp_carray

ループ内。しかし、なぜこれが必要なのか、私はまだ完全には理解していません。

使用しようとするprangeと、次のコンパイルエラーが発生します。

Error compiling Cython file:
------------------------------------------------------------
...
            # allocate a temporary array to hold the result of
            # expensive_function_1
            tmp_carray = <double*> malloc((n ** 2) * sizeof(double))

            # a 2D typed memoryview onto tmp_carray
            tmp = <double[:n, :n]> tmp_carray
               ^
------------------------------------------------------------

parallel_allocate.pyx:28:16: Memoryview slices can only be shared in parallel sections
4

2 に答える 2

6

免責事項:ここに記載されていることはすべて、一粒の塩で取る必要があります。私はそれを知っているともっと推測しています。Cython-Userで質問する必要があります。彼らはいつもフレンドリーで、すぐに答えてくれます。

Cython のドキュメントがあまり明確ではないことに同意します。

[...] 多くの場合、memoryviews は GIL を必要としません:

cpdef int sum3d(int[:, :, :] arr) nogil: ...

特に、メモリビューのインデックス作成、スライス、または転置には GIL は必要ありません。メモリビューでは、コピー メソッド (C および Fortran の連続コピー) に対して、または dtype がオブジェクトで、オブジェクト要素が読み書きされるときに GIL が必要です。

これは、メモリ ビュー パラメーターを渡したり、スライスや転置に使用したりするのに Python GIL が必要ないことを意味していると思います。ただし、メモリビューの作成またはコピーには GIL が必要です。

これを裏付けるもう 1 つの議論は、Cython 関数が Python にメモリ ビューを返すことが可能であるということです。

from cython.view cimport array as cvarray
import numpy as np

def bla():
    narr = np.arange(27, dtype=np.dtype("i")).reshape((3, 3, 3))
    cdef int [:, :, :] narr_view = narr
    return narr_view

与えます:

>>> import hello
>>> hello.bla()
<MemoryView of 'ndarray' at 0x1b03380>

これは、メモリビューが Python の GC 管理メモリに割り当てられるため、GIL を作成する必要があることを意味します。したがって、nogil セクションでメモリビューを作成することはできません


エラーメッセージについて

Memoryview スライスは、並列セクションでのみ共有できます

「スレッド専用のメモリビュー スライスを持つことはできません。スレッド共有のメモリビュー スライスでなければなりません」と読むべきだと思います。

于 2014-03-06T22:59:39.597 に答える
0

http://docs.cython.org/src/userguide/external_C_code.html#release-the-gil

"""

GILのリリース

with nogil ステートメントを使用して、コードのセクション周辺で GIL を解放できます。

 with nogil:
<code to be executed with the GIL released> Code in the body of the statement must not manipulate Python objects in any way, and must

最初に GIL を再取得せずに Python オブジェクトを操作するものを呼び出さないでください。Cython は現在これをチェックしません。

"""

于 2014-03-06T16:04:47.960 に答える