[英]Parallelize in Cython without GIL
我正在嘗試計算numpy
數組的一些列,使用cdef
函數在 for 循環中對 python 對象( numpy
數組)進行操作。
我想同時進行。 但不確定如何。
這是一個玩具示例,一個def
函數使用prange
在 for 循環中調用cdef
函數,這是不允許的,因為np.ndarray
是一個 python 對象。 在我的實際問題中,一個矩陣和一個向量是cdef
函數的參數,並且執行了一些numpy
矩陣操作,例如np.linalg.pinv()
(我猜這實際上是瓶頸)。
%%cython
import numpy as np
cimport numpy as np
from cython.parallel import prange
from c_functions import estimate_coef_linear_regression
DTYPE = np.float
ctypedef np.float_t DTYPE_t
def transpose_example(np.ndarray[DTYPE_t, ndim=2] data):
"""
Transposes a matrix. It does each row independently and parallel
"""
cdef Py_ssize_t n = data.shape[0]
cdef Py_ssize_t t = data.shape[1]
cdef np.ndarray[DTYPE_t, ndim = 2] results = np.zeros((t, n))
cdef Py_ssize_t i
for i in prange(n, nogil=True):
results[i, :] = transpose_vector(data[:, i])
return results
cdef transpose_vector(np.ndarray[DTYPE_t, ndim=1] vector):
"""
transposes a np vector
"""
return vector.transpose()
a = np.random.rand(100, 20)
transpose_example(a)
產出
Converting to Python object not allowed without gil
並行執行此操作的最佳方法是什么?
您可以在沒有 GIL 的情況下傳遞類型化 memoryview 切片( cdef transpose_vector(DTYPE_t[:] vector)
) - 這是較新類型 memoryview 語法優於np.ndarray
的主要優勢之一。
然而,
np.asarray(vector)
)。 這需要 GIL。transpose
)都需要 GIL。 這可以在with gil:
塊中完成,但是當該塊幾乎是您的整個循環時,就變得毫無意義了。transpose_vector
指定返回類型,因此它將默認為object
,這需要 GIL。 您可以指定 Cython 返回類型,但我懷疑即使返回 memoryview 切片也可能需要在某處進行一些引用計數。總結:memoryview slices,但請記住,如果沒有 GIL,您可以做的事情非常有限。 您當前的示例只是不可並行化的(但這可能主要是因為它是一個玩具示例)。
問: “並行執行此操作的最佳方法是什么?”
+
“ 我故意使用np.transpose()
來表明我必須使用 python 對象。 ”
讓我從自由地轉述亨利福特 (Henry FORD) 的名言開始:汽車中缺陷最少的部分是完全不存在的部分——它永遠不會損壞。
那些知道numpy
的內部數組對象表示如何工作的人肯定,
它幾乎需要零時間
和
它幾乎需要零內存 I/O (至少在過去十年左右確實如此)
Numpy 很聰明。
它根本不會為此移動任何數據。 它只是以大約15 [us]
的成本調整索引
執行np.transpose()
的最佳方法根本不需要任何並行化。
任何這樣做的嘗試都會導致性能np.transpose()
,因為人為地強制執行了許多無用的內存 I/O,而原生np.transpose()
從不這樣做——它只是交換索引方案,而不移動單元數據堆,所有這些都保留在它們原來的位置(包括保持任何和所有緩存一致性有效 - 所以任何下一次訪問都將從緩存中發生0.5 ~ 5 [ns]
,而不必再次支付150-350 [ns]
倍的大量費用150-350 [ns]
用於從/向物理 RAM 位置移動任何單元數據並破壞緩存線)
______1x THE PROBLEM SIZE 100 x 20 x 8 [B]
( 16 kB RAM).transpose() ~ 17 [us]
>>> r = 100; c = 20; a = np.arange(r*c).reshape( (r,c) );a.itemsize*a.size/1E6
0.016
>>> aClk.start(); _ = a.transpose(); aClk.stop() #### 16 [kB] RAM-footprint
17 #### 17 [us] !!! ZERO-COPY
____100x THE PROBLEM SIZE 1000 x 200 x 8 [B]
(1.6 MB RAM).transpose() ~ 17 [us]
>>> r = 1000; c = 200; a = np.arange(r*c).reshape( (r,c) );a.itemsize*a.size/1E6
1.6
>>> aClk.start(); _ = a.transpose(); aClk.stop() #### 1.6 [MB] RAM-footprint
17 #### 17 [us] !!! ZERO-COPY
__10000x THE PROBLEM SIZE 10000 x 2000 x 8 [B]
(160 MB RAM).transpose() ~ 16 [us]
>>> r = 10000; c = 2000; a = np.arange(r*c).reshape( (r,c) );a.itemsize*a.size/1E6
160.0
>>> aClk.start(); _ = a.transpose(); aClk.stop() #### 160.0 [MB] RAM-footprint
16 #### 16 [us] !!! ZERO-COPY
移動或 ALAP 分配和復制許多 RAM 存儲的兆字節的單元數據,就像任何prange
-d 或任何其他代碼會做的那樣,將需要很prange
,而不是像智能 numpy 設計那樣智能16 [ns]
。
>>> a.flags
C_CONTIGUOUS : True <-------------------------ORIGINAL [indirect] RAM-indexing
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>>
>>> _.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True <------------------------TRANSPOSE'd
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
QED
現在是通知背后的重點。 希望如此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.