【问题标题】:Thread-local arrays in cython's prange without huge memory allocation没有大量内存分配的 cython 的 prange 中的线程局部数组
【发布时间】:2018-08-30 15:48:20
【问题描述】:

我想使用 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])

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

我是否面临 Cython prange 的限制,我需要学习正确的 C 还是我在做/理解一些非常错误的事情?

编辑:我正在寻找的函数是openmp.omp_get_max_threads()openmp.omp_get_thread_num(),用于创建大小合理的临时数组。我必须先cimport openmp

【问题讨论】:

  • Cython 通常会正确分配线程局部变量(如果您只是将其设为标量而不是数组)。 take 失败,看看能不能把循环体放在一个单独的函数里,有自己的局部变量
  • @DavidW 感谢您的帮助。我可能应该将我的代码拆分成更小的函数,因为我需要数组。不幸的是,我正在努力弄清楚该怎么做。
  • 我会在接下来的几天里尝试写一个完整的答案,但我的建议是,如果显示的两行(temp_var = ...some_more_maths(temp_var))包含在一个函数中,那么变量是函数本地的(所以绝对是线程本地的)

标签: multithreading numpy memory-management cython gil


【解决方案1】:

这是 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

(这与您的基本相同,但可编译)。这将编译为 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++){

您可以看到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)

这样做会强制temporary_variable 位于函数的本地(因此也位于线程的本地)


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

  1. 我认为创建线程本地内存视图是不可能的。
  2. 您可以使用mallocfree 创建一个线程本地C 数组,但除非您对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)
    

【讨论】:

  • 非常感谢您的详细回答。但是,我仍然不明白如何使 temporary_variable 成为线程本地 array (请参阅我的帖子中的编辑)。也许这不是 cython 可以完成的事情,我需要重构我的代码以避免需要线程本地数组。
  • 再次感谢。 #3 是我已经在做的;问题是它需要大量的 RAM 来进行大量输入。我想#2是我需要做的,但我需要先提高我的C技能。现在,我刚刚放弃了针对这个特定案例的并行性,好吧,它给了我一个在等待结果的同时闲逛 SO 的借口。 :o)
  • 这与您在问题中显示的内容不同。您创建一个 input_size x result_size 的数组。我创建了一个 input_size x number_of_threads 的数组。 number_of_threads 通常相当小(4 还是 8?)。
  • 抱歉,我错过了。我认为这正是我想要的。我会尽快尝试的。非常感谢。
  • 有什么理由退货吗?我将cdef 调整为void 返回类型,并将temporary_variable 传递给它,所以cython -a 最终全是白色,而且它似乎可以工作。
猜你喜欢
  • 1970-01-01
  • 2012-08-12
  • 2016-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
相关资源
最近更新 更多