簡體   English   中英

在Cython中分配中間多維數組而無需獲取GIL

[英]Allocate intermediate multidimensional arrays in Cython without acquiring the GIL

我正在嘗試使用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

似乎無法編譯的原因至少有兩個:

  1. 基於cython -a的輸出, cython -a創建類型化的內存視圖:

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

    似乎涉及Python API調用,因此我無法釋放GIL以允許外部循環並行運行。

    我給人的印象是,鍵入的內存視圖不是Python對象,因此子進程應該能夠在不先獲取GIL的情況下創建它們。 是這樣嗎

2.即使我將prange(m, nogil=True)替換為正常的range(m) ,Cython仍然似乎不喜歡在內部循環中存在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

更新資料

事實證明,第二個問題很容易通過移動解決

 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

免責聲明:這里的一切都應與一粒鹽一起服用。 我更是在猜測那個知道。 您當然應該在Cython-User提問 他們總是友好而快速地回答。

我同意Cython的文檔不是很清楚:

內存視圖通常不需要GIL:

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

特別是,您不需要GIL進行memoryview索引,切片或轉置。 對於復制方法(C和Fortran連續副本),或者當dtype為object並且讀取或寫入object元素時,Memoryviews需要GIL。

我認為這意味着傳遞內存視圖參數或將其用於切片或轉置不需要Python GIL。 但是, 創建一個內存視圖或復制一個內存視圖需要GIL。

支持此功能的另一個參數是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>

這意味着memoryview是在Python的GC管理的內存中分配的,因此需要創建GIL。 因此, 您無法在nogil部分中創建memoryview


現在,關於錯誤消息的問題

Memoryview切片只能在並行部分中共享

我認為您應該將其閱讀為“您不能擁有線程專用memoryview切片。它必須是線程共享memoryview切片”。

http://docs.cython.org/src/userguide/external_C_code.html#releasing-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當前不檢查。

“”

暫無
暫無

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

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