Multithreading cython中for循环的并行化:超越prange
我正在努力使用cython正确地并行化函数。基本上,问题在于如何存储一些数据。实际代码有点长,但最终它会执行以下操作:Multithreading cython中for循环的并行化:超越prange,multithreading,multiprocessing,cython,python-multiprocessing,Multithreading,Multiprocessing,Cython,Python Multiprocessing,我正在努力使用cython正确地并行化函数。基本上,问题在于如何存储一些数据。实际代码有点长,但最终它会执行以下操作: def bin_var(double[:] dist, double[:] values, double[:] bin_def, double[:] varg, long[:] count): dbin = (bin_def[1] - bin_def[0]) / bin_def[2] f
def bin_var(double[:] dist,
double[:] values,
double[:] bin_def,
double[:] varg, long[:] count):
dbin = (bin_def[1] - bin_def[0]) / bin_def[2]
for n1 in range(values.size):
if (dist[n1] < bin_def[0]) or (dist[n1] >= bin_def[1]):
continue
else:
ni = int((dist - bin_def[0]) / dbin)
count[ni] += 1
varg[ni] += calc_something(values[ni])
# compute the mean
for n1 in range(int(bin_def[2])):
varg[ni] /= count[ni]
这段代码适用于一些简单的并行化值,并且dist非常大:需要将第一个for循环拆分到单独的进程上,每个进程处理自己版本的count和varg数组。完成后,必须在第二个for循环之前将count和varg的不同版本相加,从而将所有内容组合在一起
也就是说,我花了两天的时间试图理解如何在cython中高效地实现这一点,我开始怀疑这在当前版本的语言中是不可能的。请注意,对于第一个循环仅使用cython.parallel中的prange并不能提供正确的结果,因为我假设从不同线程同时访问ni、count和varg
cython并行支持真的如此有限吗?我得到了如此好的单线程加速,我只希望我能继续…我可以在这里想到三个选项: 使用GIL确保+=是单线程完成的:
varg_ni = calc_something(values[ni]) # keep this out
# of the single threaded block...
with gil:
count[ni] += 1
varg[ni] += varg_ni
这很简单,如果在calc_中完成的工作相当大,也不会太糟糕
使用count和varg 2D数组,每个线程写入不同的列。然后沿第二维度求和:
# rough, untested outline....
# might need to go in a `with parallel()` block
num_threads = openmp.omp_get_num_threads()
cdef double[:,:] count_tmp = np.zeros((count.shape[0],num_threads))
cdef double[:,:] varg_tmp = np.zeros((varg.shape[0],num_threads))
# then in the loop:
count_tmp[ni,cython.parallel.threadid()] += 1
varg_tmp[ni,cython.parallel.threadid()] += calc_something(values[ni])
# after the loop:
count[:] = np.sum(count_tmp,axis=1)
varg[:] = np.sum(varg_tmp,axis=1)
您也可以使用中的想法来做类似的事情
注意-GCC目前为此给了我一个内部编译器错误-我觉得它应该可以工作,但目前它似乎不工作,所以请尝试选项3,风险自负。。。使用以原子方式进行添加。这需要一些工作来绕过Cython,但应该不会太难。使用add_in place宏创建短C头文件:
_Pragma是一个C99特性,它应该允许您将Pragma放入预处理器语句中。然后告诉Cython关于该头文件的信息,就像它是一个函数一样:
cdef extern from "header.h":
void add_inplace(...) nogil # just use varargs to make Cython think it accepts anything
然后在循环中执行以下操作:
add_inplace(count[ni], 1)
add_inplace(varg[ni], calc_something(values[ni]))
因为这使用了宏技巧,所以它可能有点脆弱,即肯定不能与PyObject*s一起使用,但在使用标准C数字类型时,它应该生成正确的C代码。检查代码以确保
谢谢,我试过gil way,但它对我的calc_来说太慢了,我甚至没有提到它。另一方面,其他的选择似乎很有希望。解决方案2对我来说是可行的,但它只值得我为那些我真正感兴趣的大问题付出努力。但是请注意,prange有一些奇怪的行为,比如在循环之后强制返回。我没有尝试解决方案3,因为它对我来说有点遥不可及。我无法让解决方案3发挥作用,因此我认为建议您避免:
add_inplace(count[ni], 1)
add_inplace(varg[ni], calc_something(values[ni]))