这里是Cython入门指南。我正在尝试通过使用多线程来加速计算某个特定成对统计量(在几个bin中)。具体而言,我正在使用cython.parallel中的prange,它内部使用openMP。
以下是最小化示例,用Jupyter笔记本的Cython魔法进行编译。
笔记本设置:
产量
这不是一个完美的比例缩放,但这不是问题的重点。(如果您有超出添加常规修饰符或微调prange参数的建议,请让我知道。)
然而,这个计算显然不是线程安全的。
以下是最小化示例,用Jupyter笔记本的Cython魔法进行编译。
笔记本设置:
%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,然后简单地通过箱循环也不是一个好的选择,因为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