【问题标题】:Cython iterate over list of numpy arrays without the gilCython 在没有 gil 的情况下迭代 numpy 数组列表
【发布时间】:2017-07-02 06:05:29
【问题描述】:

我想遍历具有不同维度的 numpy 数组列表,并将它们传递给不需要 GIL 的 cython 函数:

# a has T1 rows and M columns
a = np.array([[0.0, 0.1, 0.3, 0.7],
              [0.1, 0.2, 0.1, 0.6],
              [0.1, 0.2, 0.1, 0.6]])

# b has T2 rows and M columns
b = np.array([[1.0, 0.0, 0.0, 0.0],
              [0.1, 0.2, 0.1, 0.6]])

# c has T3 rows and M columns
c = np.array([[0.1, 0.0, 0.3, 0.6],
              [0.5, 0.2, 0.3, 0.0],
              [0.0, 1.0, 0.0, 0.0],
              [0.0, 0.0, 0.1, 0.0]])

array_list = [a, b, c]
N = len(array_list)

# this function works
@cython.boundscheck(False)
@cython.wraparound(False)
cdef void function_not_requiring_the_gil(double[:, ::1] arr) nogil:
    cdef int T = arr.shape[0]
    cdef int M = arr.shape[1]
    cdef int i, t

    for t in range(T):
        for i in range(M):
            # do some arbitrary thing to the array in-place
            arr[t, i] += 0.001

# issue is with this function
def function_with_loop_over_arrays(array_list, int N):
    cdef int i

    with nogil:
        for i in range(N):
            function_not_requiring_the_gil(array_list[i])

编译 Cython 代码时,出现以下错误:

Error compiling Cython file:
------------------------------------------------------------
...
def function_with_loop_over_arrays(array_list, int N):
    cdef int i

    with nogil:
        for i in range(N):
            function_not_requiring_the_gil(array_list[i])                                                    ^
------------------------------------------------------------

my_file.pyx:312:53: Indexing Python object not allowed without gil

是否有另一种类型的数据结构可以用来代替 Python 列表来存储这些 numpy 数组,这样我就可以在没有 gil 的情况下遍历它们?我对涉及 malloc C 指针/Cython memoryviews/其他我不知道的类型的建议持开放态度。

请注意,每个 numpy 数组都有不同的行数,但数组列表的长度是已知的。

【问题讨论】:

    标签: python c numpy cython


    【解决方案1】:

    您可以将两个形状为 (3,) 的数组传递给 function_with_loop_over_arrays:一个 (array_starts) 包含指向 abc 的第一个元素的指针,以及一个 (arrays_rows ) 其中包含T1T2T3

    然后修改function_not_requiring_the_gil,使其接收数组第一个元素的指针及其行数,然后您就可以在function_with_loop_over_arrays 中调用它:

    for i in range(N):  # N is 3 and should be passed to function_with_loop_over_arrays
        function_not_requiring_the_gil(array_starts[i], array_rows[i])  
    

    【讨论】:

    • @B.M.好吧,我添加了一条评论,解释了为什么我不赞成您的回答。有什么问题吗?
    • 非常感谢您的回答,它确实解决了我的问题,但需要修改 nogil 函数以接受不同的参数。我实际上有几个我想在这个循环中调用的 nogil 函数,它们中的每一个都需要多个数组作为输入,因此必须为每个原始数组传递两个参数(起始指针和行数)真的会膨胀这些函数的参数数量。有什么方法可以让我自己传递数组/内存视图吗?如果没有,我会接受你的回答。
    • @user3570195 我不相信有一种方法可以在 cython 中操作数组而不传递它们的长度:如果我没记错的话,这就是 C 的行为方式。这就是在 cython 中进行 blas/lapack 例程函数调用的方式。
    • 我现在正在尝试实现您的方法,但对 C 不是很熟悉。为了清楚起见,它需要我将 double* 指针传递给我的 numpy 数组,然后更改我的nogil 函数以 arr[(t*M)+i] 而不是 arr[t, i] 的形式访问数组?非常感谢对所需更改的更明确的解释。再次感谢!
    【解决方案2】:

    正如错误消息所说,没有 gil,您无法索引 Python list,并且实际上没有任何明显的替代数据结构可以充当相同的角色。您只需移动 nogil 即可将索引移出它

    def function_with_loop_over_arrays(array_list, int N):
        cdef int i
        cdef double[:, ::1] tmp_mview
    
        for i in range(N):
            tmp_mview = array_list[i]
            with nogil:        
                function_not_requiring_the_gil(tmp_mview)
    

    (等效地,您可以将索引放在 with gil: 块中。)

    获取和发布gil 的成本很小,但只要您的function_not_requiring_the_gil 与索引编制相比,它做了相当多的工作,它应该是微不足道的。

    【讨论】:

    • 感谢您的回答,我了解使用列表的问题,这就是为什么我要求提供另一种传入数组的方法。我想为该循环释放 GIL,因为我将传入许多数组并希望使用多线程。答案@P。 Camilleri 提供的更接近我想要的。
    • 您仍然可以使用多线程。它会稍微慢一些,因为它必须偶尔等待每个线程获得 GIL,但它仍然会给你大部分预期的速度。
    【解决方案3】:

    nogil 的功能通常很容易从numba 控制:

    import numba
    @numba.jit(nogil=True) 
    def function_not_requiring_the_gil(arr):
        T,N = arr.shape
        for t in range(T):
            for i in range(N):
            # do some arbitrary thing to the array in-place
            arr[t, i] += 0.001
    
    def function_with_loop_over_arrays(array_list)
            for a in array_list:
                function_not_requiring_the_gil(a)
    

    会给你同样(高效)的结果。

    【讨论】:

    • 我认为这不能解决我的问题。您建议的内容仅包含单个 numpy 数组,而不是任意数组列表。我拥有的 nogil 函数实际上要复杂得多,我有几个。问题是我想在循环中对许多不同大小的 numpy 数组调用这些 nogil 函数。
    • 感谢您编辑答案,现在似乎解决了我的问题。但是,我真的很想知道是否有办法使用 cython 而不是 numba 来完成此任务,因为我需要在循环中调用的 nogil 函数已经实现。
    • 我投了反对票,因为我认为这个问题相当明确地要求 Cython 解决方案,而不是一个 numba 解决方案。
    猜你喜欢
    • 2018-06-30
    • 2017-03-25
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    • 2020-04-07
    • 1970-01-01
    • 2017-05-07
    相关资源
    最近更新 更多