我正在努力使用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]
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]
此代码适用于一些简单的并行化(values
和dist
非常大):需要将第一个for
循环拆分为不同的进程,每个进程都在自己的版本上运行count
和varg
数组。完成后,必须通过在第二个for循环(更短)之前将count
和varg
的不同版本相加来将所有内容组合在一起。
那就是说,这是两天我试图理解如何在cython
中有效地实现这一点,我开始怀疑使用当前版本的语言是不可能的。请注意,仅使用prange
中的cython.parallel
作为第一个循环无法提供正确的结果,因为(我假设)同时访问ni
,count
和{{1来自不同的线程。
varg
并行支持真的如此有限吗?我得到了这么好的加速单线程,我只是希望我能继续......
答案 0 :(得分:3)
我可以在这里考虑三个选项:
使用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_something
中完成的工作相当大,这很容易也不会太糟糕
创建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需要您自担风险...)使用openmp atomic
directive以原子方式进行添加。这需要一些工作来规避Cython,但不应该太困难。使用add_inplace
宏创建一个简短的C头文件:
#define add_inplace(x,y) _Pragma("omp atomic") x+=y
_Pragma
是一个C99功能,允许您将编译指示放在预处理程序语句中。然后告诉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*
一起使用,但它在使用标准C数字类型时应该生成正确的C代码。(检查代码是肯定)