类似于 Cython Memoryview 作为返回值,但除了破解生成的 C 代码之外,我没有看到其他解决方案。
我使用的是Cython 3.0,但看起来结果和
<3
是一样的。
这是一个例子:
# cython: language_level=3, boundscheck=False, cdivision=True, wraparound=False, initializedcheck=False, nonecheck=False
cimport cython
from cython cimport floating
import numpy as np
cimport numpy as np
np.import_array()
def test_func():
cdef np.ndarray[float, ndim=2] arr = np.zeros((5, 5), dtype=np.float32)
cdef float[:, ::1] arr_view = arr
_run(arr_view)
cdef void _run(floating[:, ::1] arr_view) noexcept nogil:
cdef floating[:, :] tmp = _get_upper_left_corner(arr_view)
cdef inline floating[:, :] _get_upper_left_corner(floating[:, ::1] arr) noexcept nogil:
return arr[:-1, :-1]
然后运行
cython -a cython_test.pyx
,它显示 _get_upper_left_corner
函数具有包含 GIL 获取的内存视图初始化代码,并且 _run
函数具有错误检查,因为 _get_upper_left_corner
函数可能会返回错误(至少这是我的猜测):
+17: cdef inline floating[:, :] _get_upper_left_corner(floating[:, ::1] arr) noexcept nogil:
static CYTHON_INLINE __Pyx_memviewslice __pyx_fuse_0__pyx_f_11cython_test__get_upper_left_corner(__Pyx_memviewslice __pyx_v_arr) {
__Pyx_memviewslice __pyx_r = { 0, 0, { 0 }, { 0 }, { 0 } };
/* … */
/* function exit code */
__pyx_L1_error:;
#ifdef WITH_THREAD
__pyx_gilstate_save = __Pyx_PyGILState_Ensure();
#endif
__PYX_XCLEAR_MEMVIEW(&__pyx_t_1, 1);
__pyx_r.data = NULL;
__pyx_r.memview = NULL;
__Pyx_AddTraceback("cython_test._get_upper_left_corner", __pyx_clineno, __pyx_lineno, __pyx_filename);
goto __pyx_L2;
__pyx_L0:;
if (unlikely(!__pyx_r.memview)) {
#ifdef WITH_THREAD
PyGILState_STATE __pyx_gilstate_save = __Pyx_PyGILState_Ensure();
#endif
PyErr_SetString(PyExc_TypeError, "Memoryview return value is not initialized");
#ifdef WITH_THREAD
__Pyx_PyGILState_Release(__pyx_gilstate_save);
#endif
}
#ifdef WITH_THREAD
__Pyx_PyGILState_Release(__pyx_gilstate_save);
#endif
__pyx_L2:;
return __pyx_r;
}
我假设内存视图上的切片会创建一个新的结构。如果必须的话,我可以接受结构体的初始化,但我真的不希望获取 GIL。有没有办法在没有 GIL 的情况下完成返回的内存视图?如果我需要初始化某些东西,我怎样才能在不复制可能很大的 numpy 数组的情况下做到这一点(我只是在读它)。
您在生成的 C 代码中看到的 GIL(全局解释器锁)获取是由于 Cython 在多线程场景中确保内存视图一致性的默认行为。不幸的是,即使您只读取内存视图,这也可能会导致 GIL 获取。如果您确定您的使用不会因多线程而导致冲突,您可以使用
wraparound
指令明确告诉 Cython 您不需要 GIL 来执行这些内存视图操作。
将
wraparound=False
添加到代码顶部的 cython
指令,如下所示:
# cython: language_level=3, boundscheck=False, cdivision=True, wraparound=False, initializedcheck=False, nonecheck=False
这应该可以防止 Cython 在生成的 C 代码中插入 GIL 获取以进行内存视图切片操作,并且它应该有助于实现在没有 GIL 参与的情况下返回内存视图的目标。只要确保您的代码在多线程上下文中确实可以安全运行而无需 GIL。
请记住,在内存视图操作中禁用 GIL 保护可能需要格外小心,因为您要承担线程安全的责任。确保彻底测试您的代码以确保其行为符合预期。