python中的并行化(非对称)循环

python中的并行化(非对称)循环,python,optimization,parallel-processing,Python,Optimization,Parallel Processing,下面的代码是用python编写的,它可以工作,即返回预期的结果。然而,它是非常缓慢的,我相信可以优化 G_tensor = numpy.matlib.identity(N_particles*3,dtype=complex) for i in range(N_particles): for j in range(i, N_particles): if i != j: #Do lots of things, here is shown an ex

下面的代码是用python编写的,它可以工作,即返回预期的结果。然而,它是非常缓慢的,我相信可以优化

G_tensor = numpy.matlib.identity(N_particles*3,dtype=complex)

for i in range(N_particles):
    for j in range(i, N_particles):
        if i != j:

            #Do lots of things, here is shown an example.
            # However you should not be scared because 
            #it only fills the G_tensor
            R = numpy.linalg.norm(numpy.array(positions[i])-numpy.array(positions[j]))
            rx = numpy.array(positions[i][0])-numpy.array(positions[j][0])
            ry = numpy.array(positions[i][1])-numpy.array(positions[j][1])
            rz = numpy.array(positions[i][2])-numpy.array(positions[j][2])
            krq = (k*R)**2
            pf = -k**2*alpha*numpy.exp(1j*k*R)/(4*math.pi*R)
            a = 1.+(1j*k*R-1.)/(krq)
            b = (3.-3.*1j*k*R-krq)/(krq) 
            G_tensor[3*i+0,3*j+0] = pf*(a + b * (rx*rx)/(R**2))  #Gxx
            G_tensor[3*i+1,3*j+1] = pf*(a + b * (ry*ry)/(R**2))  #Gyy
            G_tensor[3*i+2,3*j+2] = pf*(a + b * (rz*rz)/(R**2))  #Gzz
            G_tensor[3*i+0,3*j+1] = pf*(b * (rx*ry)/(R**2))      #Gxy
            G_tensor[3*i+0,3*j+2] = pf*(b * (rx*rz)/(R**2))      #Gxz
            G_tensor[3*i+1,3*j+0] = pf*(b * (ry*rx)/(R**2))      #Gyx
            G_tensor[3*i+1,3*j+2] = pf*(b * (ry*rz)/(R**2))      #Gyz
            G_tensor[3*i+2,3*j+0] = pf*(b * (rz*rx)/(R**2))      #Gzx
            G_tensor[3*i+2,3*j+1] = pf*(b * (rz*ry)/(R**2))      #Gzy

            G_tensor[3*j+0,3*i+0] = pf*(a + b * (rx*rx)/(R**2))  #Gxx
            G_tensor[3*j+1,3*i+1] = pf*(a + b * (ry*ry)/(R**2))  #Gyy
            G_tensor[3*j+2,3*i+2] = pf*(a + b * (rz*rz)/(R**2))  #Gzz
            G_tensor[3*j+0,3*i+1] = pf*(b * (rx*ry)/(R**2))      #Gxy
            G_tensor[3*j+0,3*i+2] = pf*(b * (rx*rz)/(R**2))      #Gxz
            G_tensor[3*j+1,3*i+0] = pf*(b * (ry*rx)/(R**2))      #Gyx
            G_tensor[3*j+1,3*i+2] = pf*(b * (ry*rz)/(R**2))      #Gyz
            G_tensor[3*j+2,3*i+0] = pf*(b * (rz*rx)/(R**2))      #Gzx
            G_tensor[3*j+2,3*i+1] = pf*(b * (rz*ry)/(R**2))      #Gzy
你知道我如何将其并行化吗?您应该注意,这两个循环不是对称的

编辑一:上面给出了一个Nuppython解决方案,并对Python和TythPyththic中的C++实现、我的循环版本进行了比较。结果如下: -C++=0.14SEG -numpythonic版本=1.39seg -python循环版本=46.56seg


如果我们使用英特尔版本的numpy,结果可能会更好。

Python不是一种快速语言。python的数字运算应该始终用于以编译语言编写的时间关键部分代码。将编译降低到CPU级别后,您可以将代码的速度提高到100倍,然后仍然可以进行并行化。因此,我不会贬低使用更多内核来做低效的事情,而是更高效地工作。我看到了以下加速代码的方法:

1) 更好地使用numpy:可以直接在向量/矩阵级别上在标量级别上进行计算吗?例如,rx=位置[:,0]-位置[0,:](如果正确,则未检查),但沿着这些线的某些东西

如果你的计算方式不可能做到这一点,那么你可以选择2号或3号选项

2) 使用cython。Cython将Python代码编译为C,然后编译到CPU。通过在正确的位置使用静态键入,您可以使代码更快,请参阅cython教程,例如:

3) 如果您熟悉FORTRAN,最好用FORTRAN编写这一部分,然后使用f2py从Python调用它。事实上,您的代码看起来很像FORTRAN。对于C和C++,SWIG是一个伟大的工具,使编译代码在Python中可用,但是还有很多其他的技术(Cython,Booo::Python,CyType,NUBA等等)


当您完成了这项工作,但速度仍然很慢时,可以选择将GPU power与pyCUDA结合使用,或将mpi4py与并行化或多处理结合使用。

Python不是一种快速语言。python的数字运算应该始终用于以编译语言编写的时间关键部分代码。将编译降低到CPU级别后,您可以将代码的速度提高到100倍,然后仍然可以进行并行化。因此,我不会贬低使用更多内核来做低效的事情,而是更高效地工作。我看到了以下加速代码的方法:

1) 更好地使用numpy:可以直接在向量/矩阵级别上在标量级别上进行计算吗?例如,rx=位置[:,0]-位置[0,:](如果正确,则未检查),但沿着这些线的某些东西

如果你的计算方式不可能做到这一点,那么你可以选择2号或3号选项

2) 使用cython。Cython将Python代码编译为C,然后编译到CPU。通过在正确的位置使用静态键入,您可以使代码更快,请参阅cython教程,例如:

3) 如果您熟悉FORTRAN,最好用FORTRAN编写这一部分,然后使用f2py从Python调用它。事实上,您的代码看起来很像FORTRAN。对于C和C++,SWIG是一个伟大的工具,使编译代码在Python中可用,但是还有很多其他的技术(Cython,Booo::Python,CyType,NUBA等等)


当您完成了这项工作,但速度仍然很慢时,可以选择将GPU power与pyCUDA结合使用,或将mpi4py与并行化或多处理结合使用。

这里有一个建议现在应该可以工作了(我纠正了一些错误)但这将让您大致了解如何将verctorization应用到代码中,以便有效地使用numpy数组。一切都是在“一次传递”(即没有任何for循环)中构建的,这是一种“numpythonic”方式:


这里有一个建议,现在应该可以工作了(我纠正了一些错误)但这仍然可以让您大致了解如何将verctorization应用到代码中,以便有效地使用numpy数组。一切都是在“一次传递”(即没有任何for循环)中构建的,这是一种“numpythonic”方式:


你的问题不是真正的并行化,而是矢量化:在numpy中,你不应该对范围(N)中的i使用
:arr[i]=1
正确的方法是
arr[:]=1
。问题是,为什么你不使用n,n,3,3的形状来代替你的张量,而不是n=3,n*1?3,我是C++程序员,所以这可能是错误的原因。第二个问题我不清楚。最后,我需要一个3*nx3*N矩阵(出于数学原因)。有可能把一个N,N,3,3转换成一个3*N,3*N张量吗?澄清一下:我说的是因为,对我来说,张量通常是N维的对象,所以使用N-dim数组来表示它们是很自然的。那么是的,绝对有可能转换成3*N,3*N;伪代码中的一些示例:使用数组(N,3,N,3)
arr[i,x_i,j,x_j]。重塑(N*3,N*3)
相当于您的
arr[3*i+x_i,3*j+x_j]
。数组(N,N,3,3)必须先转置到(N,3,N,3),然后才能进行整形
arr[i,j,x_i,x_j]。转置([0,2,1,3])。整形(N*3,N*3)
。感谢您的基准测试!我刚刚发现了一个小小的修改,它将“numpytonic”的速度提高了一倍:通过
np.ogrid
更改
np.mgrid
。你的问题不是真正的并行化,而是矢量化:在numpy中,你永远不应该对范围(N)内的I使用
:arr[I]=1
正确的方法是
arr[:]=1
。问题是,为什么你不使用n,n,3,3的形状来代替你的张量,而不是n=3,n*1?3,我是C++程序员,所以这可能是错误的原因。第二个问题我不清楚。最后,我需要一个3*nx3*N矩阵(出于数学原因)。有可能把一个N,N,3,3转换成一个3*N,3*N张量吗?澄清一下:我这么说是因为
import numpy as np
import math
N=2
k,alpha=1,1
G = np.zeros((N,3,N,3),dtype=complex)

# np.mgrid gives convenient arrays of indices that 
# can be used to write readable code
i,x_i,j,x_j = np.ogrid[0:N,0:3,0:N,0:3]
# A quick demo on how we can make the identity tensor with it
G[np.where((i == j) & (x_i == x_j))] = 1
#print(G.reshape(N*3,N*3))

positions=np.random.rand(N,3)
# Here I assumed position has shape [N,3] 
# I build arr[i,j]=position[i] - position[j] using broadcasting 
# by turning position into a column and a row 
R = np.linalg.norm(positions[None,:,:]-positions[:,None,:],axis=-1)
# R is now a N,N matrix of all the distances
#we reshape R to N,1,N,1 so that it can be broadcated to N,3,N,3 
R=R.reshape(N,1,N,1)

r=positions[None,:,:]-positions[:,None,:]

krq = (k*R)**2
pf = -k**2*alpha*np.exp(1j*k*R)/(4*math.pi*R)
a = 1.+(1j*k*R-1.)/(krq)
b = (3.-3.*1j*k*R-krq)/(krq)

#print(np.isnan(pf[:,0,:,0]))

# here we build all the combination rx*rx rx*ry etc...
comb_r=(r[:,:,:,None]*r[:,:,None,:]).transpose([0,2,1,3])
#we compute G without the pf*A term
G = pf*(b * comb_r/(R**2))
#we add pf*a term where it is due
G[np.where(x_i == x_j)] = (G + pf*a)[np.where(x_i == x_j)]
# we didn't bother with the identity or condition i!=j so we enforce it here
G[np.where(i == j)] = 0
G[np.where((i == j) & (x_i == x_j))] = 1

print(G.reshape(N*3,N*3))