Cython启动器在这里。我试图通过使用多个线程来加速计算某些成对统计量(在几个分箱中)。特别是,我使用cython.parallel中的prange,它在内部使用openMP。Cython:使prange并行化线程安全
下面的最小例子说明了这个问题(通过Jupyter笔记本Cython magic编译)。
笔记本设置:
%load_ext Cython
import numpy as np
用Cython代码:
%%cython --compile-args=-fopenmp --link-args=-fopenmp -a
from cython cimport boundscheck
import numpy as np
from cython.parallel cimport prange, parallel
@boundscheck(False)
def my_parallel_statistic(double[:] X, double[:,::1] bins, int num_threads):
cdef:
int N = X.shape[0]
int nbins = bins.shape[0]
double Xij,Yij
double[:] Z = np.zeros(nbins,dtype=np.float64)
int i,j,b
with nogil, parallel(num_threads=num_threads):
for i in prange(N,schedule='static',chunksize=1):
for j in range(i):
#some pairwise quantities
Xij = X[i]-X[j]
Yij = 0.5*(X[i]+X[j])
#check if in bin
for b in range(nbins):
if (Xij < bins[b,0]) or (Xij > bins[b,1]):
continue
Z[b] += Xij*Yij
return np.asarray(Z)
模拟数据和箱
X = np.random.rand(10000)
bin_edges = np.linspace(0.,1,11)
bins = np.array([bin_edges[:-1],bin_edges[1:]]).T
bins = bins.copy(order='C')
时序经由
%timeit my_parallel_statistic(X,bins,1)
%timeit my_parallel_statistic(X,bins,4)
个
产量
1 loop, best of 3: 728 ms per loop
1 loop, best of 3: 330 ms per loop
这是不是一个完美的比例,但是这不是问题的重点。 (但不要让我知道,如果你有超越添加常用的装饰或微调PRANGE参数的建议。)
然而,这种计算显然不是线程安全的:
Z1 = my_parallel_statistic(X,bins,1)
Z4 = my_parallel_statistic(X,bins,4)
np.allclose(Z1,Z4)
透着显著差异在这两个结果之间(在这个例子中高达20%)。
我强烈怀疑,问题是,多个线程可以在同一时间做
Z[b] += Xij*Yij
。但是我不知道如何在不牺牲加速的情况下解决这个问题。
在我的实际使用情况中,Xij和Yij的计算更加昂贵,因此我希望每对执行一次。此外,预先计算和存储所有对的Xij和Yij,然后简单地循环通过bin不是一个好选择,因为N可以变得非常大,并且我不能在内存中存储100,000 x 100,000个numpy数组(这实际上是在Cython中重写它的主要动机!)。
系统信息(中添加注释如下建议):
CPU(s): 8
Model name: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
OS: Red Hat Linux v6.8
Memory: 16 GB
每个线程中的动作是否真的独立于任何其他动作?首先运行哪一个是否重要?如果存在任何类型的依赖性,这不适合并行操作。 – hpaulj
只要每个线程创建自己的Xij和Yij,他们应该是独立的(但也许这是问题?)就数学而言,Xij和Yij独立计算每对(i,j) ,因此也是对统计量Z的贡献。 – user4319496
谢谢你在你的问题中包含如此出色的[mcve]!这样一个经过深入研究和制定的问题在SO上太稀缺了。你可能包含的唯一东西是你的CPU模型和内存来评论性能,但这不是问题的主要观点。 – Zulan