3

我不太明白如何在大型/更长的 nogil 部分中使用 memoryviews 完成引用计数。让我们假设基本上我所有的代码都是 nogil,除了在深处创建一个 numpy-array-to-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() 返回 memoryview 时,numpy 数组的引用计数仍应为 1。Cython 之后如何处理引用计数?我的意思是即使内存视图/数组被取消引用,也不允许更改引用计数,对吗?如果上面有几个带有 nogil 代码的层,它怎么知道取消对 memoryview 的引用,并且可能与 someFun() 不同,memoryview 没有向上返回?

编辑:所以我想出了一个相当粗略的方法来做更多的测试。我的代码现在看起来像这样。

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()

对我来说有趣的部分是在第一站mv仍然分配了数组/内存视图,但是当我取消引用它直到第二站才被释放。我只检查了内存使用情况htop(这就是为什么选择如此大的数组),可能有更好的方法。显然,我想要发生的自由/引用计数行为,但是当它没有 GIL 时它会这样做很奇怪。也许内存视图不完全是nogil?

有人可以解释这是否是可靠的行为吗?

4

1 回答 1

1

在 nogil 块中更新 memoryview 的引用计数与您的函数的发生方式相同someFunnogil它获取 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. 它的实现可以在这里查看

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 循环中。

于 2020-07-28T09:30:00.557 回答