在没有大量内存分配的情况下,cython 的 prange 中的线程本地数组

nic*_*oco 1 multithreading memory-management numpy cython gil

我有一些我想使用 Cython 并行执行的独立计算。

现在我正在使用这种方法:

import numpy as np
cimport numpy as cnp
from cython.parallel import prange

[...]

cdef cnp.ndarray[cnp.float64_t, ndim=2] temporary_variable = \
    np.zeros((INPUT_SIZE, RESULT_SIZE), np.float64)
cdef cnp.ndarray[cnp.float64_t, ndim=2] result = \
    np.zeros((INPUT_SIZE, RESULT_SIZE), np.float64)

for i in prange(INPUT_SIZE, nogil=True):
    for j in range(RESULT_SIZE):
        [...]
        temporary_variable[i, j] = some_very_heavy_mathematics(my_input_array)
        result[i, j] = some_more_maths(temporary_variable[i, j])
Run Code Online (Sandbox Code Playgroud)

这种方法有效,但我的问题来自事实上我需要几个temporary_variables。这会在增长时导致大量内存使用INPUT_SIZE。但我相信真正需要的是每个线程中的临时变量。

我是否面临 Cython 的恶作剧的限制,我是否需要学习正确的 C 语言,或者我是否在做/理解一些非常错误的事情?

编辑:我一直在寻找的功能都openmp.omp_get_max_threads()openmp.omp_get_thread_num()创建合理大小的临时数组。我必须cimport openmp先。

Dav*_*idW 5

这是 Cython 试图检测的事情,并且实际上大部分时间都是正确的。如果我们拿一个更完整的示例代码:

import numpy as np
from cython.parallel import prange

cdef double f1(double[:,:] x, int i, int j) nogil:
    return 2*x[i,j]

cdef double f2(double y) nogil:
    return y+10

def example_function(double[:,:] arr_in):
    cdef double[:,:] result = np.zeros(arr_in.shape)
    cdef double temporary_variable
    cdef int i,j
    for i in prange(arr_in.shape[0], nogil=True):
        for j in range(arr_in.shape[1]):
            temporary_variable = f1(arr_in,i,j)
            result[i,j] = f2(temporary_variable)
    return result
Run Code Online (Sandbox Code Playgroud)

(这与您的基本相同,但可编译)。这将编译为 C 代码:

#pragma omp for firstprivate(__pyx_v_i) lastprivate(__pyx_v_i) lastprivate(__pyx_v_j) lastprivate(__pyx_v_temporary_variable)
                #endif /* _OPENMP */
                for (__pyx_t_8 = 0; __pyx_t_8 < __pyx_t_9; __pyx_t_8++){
Run Code Online (Sandbox Code Playgroud)

您可以看到它temporary_variable设置为线程本地。如果 Cython 没有正确检测到这一点(我发现它通常过于热衷于减少变量),那么我的建议是将循环的(部分)内容封装在一个函数中:

cdef double loop_contents(double[:,:] arr_in, int i, int j) nogil:
    cdef double temporary_variable
    temporary_variable = f1(arr_in,i,j)
    return f2(temporary_variable)
Run Code Online (Sandbox Code Playgroud)

这样做会强制temporary_variable局部于函数(以及线程)


关于创建线程本地数组:我不是 100% 清楚你想要做什么,但我会尝试猜测......

  1. 我不相信创建线程本地内存视图是可能的。
  2. 您可以使用mallocand创建线程本地 C 数组,free但除非您对 C有很好的理解,否则我不会推荐它。
  3. 最简单的方法是分配一个二维数组,其中每个线程都有一列。该数组是共享的,但由于每个线程只接触自己的无关紧要的列。一个简单的例子:

    cdef double[:] f1(double[:,:] x, int i) nogil:
        return x[i,:]
    
    def example_function(double[:,:] arr_in):
        cdef double[:,:] temporary_variable = np.zeros((arr_in.shape[1],openmp.omp_get_max_threads()))
        cdef int i
        for i in prange(arr_in.shape[0],nogil=True):
            temporary_variable[:,openmp.omp_get_thread_num()] = f1(arr_in,i)
    
    Run Code Online (Sandbox Code Playgroud)