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 つあります。
の出力に基づいて
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