使用 nogil 对内存视图进行引用计数
Reference counting of memoryviews with nogil
我不太明白 large/longer nogil 部分中的内存视图是如何进行引用计数的。让我们假设基本上我所有的代码都是 nogil,除了在深处创建一个 numpy-array-to-memoryview。 memoryview返回并向上使用。
一个相当简单的例子是
import numpy as np
cdef:
double[::1] mv
cdef double[::1] someFun(int nn) nogil:
cdef:
double[::1] mvb
with gil:
mvb = np.arange(nn, dtype=np.double)
return mvb
with nogil:
mv = someFun(30)
# Here could be MUCH more "nogil" code
# How is memory management/reference counting done here?
我假设当 someFun() returns 内存视图时,numpy 数组的引用计数仍应为 1。
Cython 之后如何处理重新计票?我的意思是即使取消引用 memoryview/array 也不允许更改引用计数,对吧?如果上面有多个带有 nogil 代码的层,它怎么知道取消引用内存视图,并且可能与 someFun() 不同,内存视图没有向上返回?
编辑: 所以我想出了一个相当粗略的方法来做更多的测试。我的代码现在看起来像这样。
import numpy as np
cdef extern from "stdio.h":
int getchar() nogil
int printf(const char* formatt, ...) nogil
cdef:
double[::1] mv, mv2 = np.ones(3)
int ii, leng = 140000000
cdef double[::1] someFun(int nn) nogil:
cdef:
double[::1] mvb
with gil:
mvb = np.ones(nn, dtype=np.double)
return mvb
with nogil:
mv = someFun(leng)
printf("1st stop")
getchar()
mv = mv2
printf("2nd stop")
getchar()
对我来说有趣的部分是,在第 1 站 array/memoryview mv
仍然被分配,但是当我取消引用时它会被释放直到第 2 站。我只用 htop
检查内存使用情况(这就是为什么选择这么大的数组),可能有更好的方法。
显然 free/refcounting 的行为是我想要发生的,但奇怪的是它在没有 GIL 的情况下会这样做。也许 memoryviews 不是完全 nogil?
有人可以解释一下这是否可靠吗?
在 nogil 块中更新内存视图的引用计数与您的函数 someFun
的方式相同 nogil
:它获取 gil 以更新引用计数。
行
with nogil:
mv = someFun(leng)
被翻译成以下 C 代码:
__pyx_t_3 = __pyx_f_3foo_someFun(__pyx_v_3foo_leng); if (unlikely(!__pyx_t_3.memview)) __PYX_ERR(0, 18, __pyx_L3_error)
__PYX_XDEC_MEMVIEW(&__pyx_v_3foo_mv, 0);
__pyx_v_3foo_mv = __pyx_t_3;
__pyx_t_3.memview = NULL;
__pyx_t_3.data = NULL;
为了绑定到新值,必须更新旧值的引用计数,这发生在 __PYX_XDEC_MEMVIEW
中。它的实现可以查阅 here:
static CYTHON_INLINE void __Pyx_XDEC_MEMVIEW({{memviewslice_name}} *memslice,
int have_gil, int lineno) {
...
} else if (likely(old_acquisition_count == 1)) {
// Last slice => discard owned Python reference to memoryview object.
if (have_gil) {
Py_CLEAR(memslice->memview);
} else {
PyGILState_STATE _gilstate = PyGILState_Ensure();
Py_CLEAR(memslice->memview);
PyGILState_Release(_gilstate);
}
...
}
这意味着如果我们没有 gil(__Pyx_XDEC_MEMVIEW
用第二个参数调用 = 0
),它将被获取以确保引用计数正确完成。
上述结果是,重新绑定内存视图并不便宜,因为它需要获取 GIL,因此应避免在紧密的 nogil 循环中。
我不太明白 large/longer nogil 部分中的内存视图是如何进行引用计数的。让我们假设基本上我所有的代码都是 nogil,除了在深处创建一个 numpy-array-to-memoryview。 memoryview返回并向上使用。
一个相当简单的例子是
import numpy as np
cdef:
double[::1] mv
cdef double[::1] someFun(int nn) nogil:
cdef:
double[::1] mvb
with gil:
mvb = np.arange(nn, dtype=np.double)
return mvb
with nogil:
mv = someFun(30)
# Here could be MUCH more "nogil" code
# How is memory management/reference counting done here?
我假设当 someFun() returns 内存视图时,numpy 数组的引用计数仍应为 1。 Cython 之后如何处理重新计票?我的意思是即使取消引用 memoryview/array 也不允许更改引用计数,对吧?如果上面有多个带有 nogil 代码的层,它怎么知道取消引用内存视图,并且可能与 someFun() 不同,内存视图没有向上返回?
编辑: 所以我想出了一个相当粗略的方法来做更多的测试。我的代码现在看起来像这样。
import numpy as np
cdef extern from "stdio.h":
int getchar() nogil
int printf(const char* formatt, ...) nogil
cdef:
double[::1] mv, mv2 = np.ones(3)
int ii, leng = 140000000
cdef double[::1] someFun(int nn) nogil:
cdef:
double[::1] mvb
with gil:
mvb = np.ones(nn, dtype=np.double)
return mvb
with nogil:
mv = someFun(leng)
printf("1st stop")
getchar()
mv = mv2
printf("2nd stop")
getchar()
对我来说有趣的部分是,在第 1 站 array/memoryview mv
仍然被分配,但是当我取消引用时它会被释放直到第 2 站。我只用 htop
检查内存使用情况(这就是为什么选择这么大的数组),可能有更好的方法。
显然 free/refcounting 的行为是我想要发生的,但奇怪的是它在没有 GIL 的情况下会这样做。也许 memoryviews 不是完全 nogil?
有人可以解释一下这是否可靠吗?
在 nogil 块中更新内存视图的引用计数与您的函数 someFun
的方式相同 nogil
:它获取 gil 以更新引用计数。
行
with nogil:
mv = someFun(leng)
被翻译成以下 C 代码:
__pyx_t_3 = __pyx_f_3foo_someFun(__pyx_v_3foo_leng); if (unlikely(!__pyx_t_3.memview)) __PYX_ERR(0, 18, __pyx_L3_error)
__PYX_XDEC_MEMVIEW(&__pyx_v_3foo_mv, 0);
__pyx_v_3foo_mv = __pyx_t_3;
__pyx_t_3.memview = NULL;
__pyx_t_3.data = NULL;
为了绑定到新值,必须更新旧值的引用计数,这发生在 __PYX_XDEC_MEMVIEW
中。它的实现可以查阅 here:
static CYTHON_INLINE void __Pyx_XDEC_MEMVIEW({{memviewslice_name}} *memslice,
int have_gil, int lineno) {
...
} else if (likely(old_acquisition_count == 1)) {
// Last slice => discard owned Python reference to memoryview object.
if (have_gil) {
Py_CLEAR(memslice->memview);
} else {
PyGILState_STATE _gilstate = PyGILState_Ensure();
Py_CLEAR(memslice->memview);
PyGILState_Release(_gilstate);
}
...
}
这意味着如果我们没有 gil(__Pyx_XDEC_MEMVIEW
用第二个参数调用 = 0
),它将被获取以确保引用计数正确完成。
上述结果是,重新绑定内存视图并不便宜,因为它需要获取 GIL,因此应避免在紧密的 nogil 循环中。