Python OpenMP和Cython的加速不足和错误结果

Python OpenMP和Cython的加速不足和错误结果,python,c,multithreading,openmp,cython,Python,C,Multithreading,Openmp,Cython,我正在尝试用OpenMP加速用Cython编写的一段简单代码。这是一个双循环,对于输入数组中的每个位置,在每个参考点添加一个数量。以下是代码的主要部分: cimport cython import numpy as np cimport numpy as np cimport openmp DTYPE = np.double ctypedef np.double_t DTYPE_t cdef extern from "math.h" nogil : DTYPE_t sqrt(DTYPE

我正在尝试用OpenMP加速用Cython编写的一段简单代码。这是一个双循环,对于输入数组中的每个位置,在每个参考点添加一个数量。以下是代码的主要部分:

cimport cython  
import numpy as np
cimport numpy as np
cimport openmp
DTYPE = np.double
ctypedef np.double_t DTYPE_t

cdef extern from "math.h" nogil :
  DTYPE_t sqrt(DTYPE_t)


@cython.cdivision(True)
@cython.boundscheck(False)
def summation(np.ndarray[DTYPE_t,ndim=2] pos, np.ndarray[DTYPE_t,ndim=1] weights, 
              np.ndarray[DTYPE_t, ndim=2] points, int num_threads = 0):

    from cython.parallel cimport prange, parallel, threadid

    if num_threads <= 0 : 
        num_threads = openmp.omp_get_num_procs()

    if num_threads > openmp.omp_get_num_procs() : 
        num_threads = openmp.omp_get_num_procs()

    openmp.omp_set_num_threads(num_threads)

    cdef unsigned int nips = len(points)
    cdef np.ndarray[DTYPE_t, ndim=1] sum_array = np.zeros(nips, dtype = np.float64)
    cdef np.ndarray[DTYPE_t, ndim=2] sum_array3d = np.zeros((nips,3), dtype = np.float64)

    cdef unsigned int n = len(weights)

    cdef unsigned int pi, i, id
    cdef double dx, dy, dz, dr, weight_i, xi,yi,zi

    print 'num_threads = ', openmp.omp_get_num_threads()

    for i in prange(n,nogil=True,schedule='static'):
        weight_i = weights[i]
        xi = pos[i,0]
        yi = pos[i,1]
        zi = pos[i,2]
        for pi in range(nips) :
            dx = points[pi,0] - xi
            dy = points[pi,1] - yi
            dz = points[pi,2] - zi
            dr = 1.0/sqrt(dx*dx + dy*dy + dz*dz)
            sum_array[pi] += weight_i * dr
            sum_array3d[pi,0] += weight_i * dx
            sum_array3d[pi,1] += weight_i * dy
            sum_array3d[pi,2] += weight_i * dz


    return sum_array, sum_array3d
cimport cython
将numpy作为np导入
cimport numpy作为np
cimport openmp
DTYPE=np.double
ctypedef np.double\u t DTYPE\t
来自“math.h”nogil的cdef外部代码:
数据类型sqrt(数据类型)
@cython.cdivision(真)
@cython.boundscheck(错误)
def总和(np.ndarray[DTYPE_t,ndim=2]位置,np.ndarray[DTYPE_t,ndim=1]权重,
np.ndarray[DTYPE_t,ndim=2]点,int num_threads=0):
来自cython.parallel cimport prange,parallel,threadid
如果num_线程openmp.omp_get_num_procs():
num\u threads=openmp.omp\u get\u num\u procs()
openmp.omp\u set\u num\u线程(num\u线程)
cdef无符号整数nips=len(点)
cdef np.ndarray[DTYPE_t,ndim=1]和数组=np.zero(nips,DTYPE=np.float64)
cdef np.ndarray[DTYPE_t,ndim=2]sum_array3d=np.zeros((nips,3),DTYPE=np.float64)
cdef无符号整数n=len(权重)
cdef无符号整数pi,i,id
双DX、DY、DZ、DR、WEY、I、席、彝、子
打印'num_threads=',openmp.omp_get_num_threads()
对于prange中的i(n,nogil=True,schedule='static'):
重量i=重量[i]
席=POS〔I,0〕
yi=pos[i,1]
zi=位置[i,2]
对于范围内的pi(nips):
DX=点[PI,0 ]席
dy=点[pi,1]-yi
dz=点[pi,2]-zi
dr=1.0/sqrt(dx*dx+dy*dy+dz*dz)
和数组[pi]+=权重*dr
求和数组3d[pi,0]+=权重*dx
和数组3d[pi,1]+=权重
和数组3d[pi,2]+=权重
返回和数组,和数组3d
我已经将其与相关的测试和设置文件()

出现了两个问题:

首先,在当前配置中,我无法获得任何加速。代码在多个内核上运行,但是计时没有显示出任何优势

第二,结果是不同的,这取决于表明存在 比赛条件。现有的金额不应该最终被削减吗?还是因为它是一个嵌套的for循环而发生了一些有趣的事情?我认为
prange
中的所有内容都是在每个线程中单独执行的

如果我颠倒循环的顺序,这两种情况都会消失——但是因为我的外部循环现在的结构是所有数据读取都在这里完成,如果我颠倒它们,数组将遍历num_线程次数,这是浪费。我还尝试将整个嵌套循环放入带有parallel()的
块中:
并显式使用线程本地缓冲区,但未能使其工作


很明显,我遗漏了一些关于OpenMP应该如何工作的基本信息(尽管这可能是Cython特有的),所以我非常感谢您的提示

您是否尝试过切换两个循环,以避免多个线程从同一位置读写?我非常确定这些循环不会自动升级为OpenMP缩减,并且诸如“sum_array3d[pi,0]+=weight_I*dx”等增量不是原子级的

此外,由于计算相对简单,Cython可能有些过分,您可以使用其中一个或另一个

默认情况下,长尾鹦鹉将使用OpenMP并行执行理解。您必须重写代码,使其看起来像:

@parakeet.jit
def summation(pos, weights, points):
  n_points = len(points)
  n_weights = len(weights)
  sum_array3d = np.zeros((n_points,3))
  def compute(i):
    pxi = points[i, 0]
    pyi = points[i, 1]
    pzi = points[i, 2]
    total = 0.0
    for j in xrange(n_weights):
      weight_j = weights[j]
      xj = pos[j,0]
      yj = pos[j,1]
      zj = pos[j,2]
      dx = pxi - pos[j, 0]
      dy = pyi - pos[j, 1]
      dz = pzi - pos[j, 2]
      dr = 1.0/np.sqrt(dx*dx + dy*dy + dz*dz)
      total += weight_j * dr
      sum_array3d[i,0] += weight_j * dx
      sum_array3d[i,1] += weight_j * dy
      sum_array3d[i,2] += weight_j * dz
    return total 
  sum_array = np.array([compute(i) for i in xrange(n_points)])
  return sum_array, sum_array3d
至于Numba,我不确定这个构造是否已经进入了免费版本


编辑:额击,对不起,我错过了你问题中考虑切换循环的部分

也许用
c
c++
重新标记将帮助您获得更多视图和可能的一些答案?嗨@alex rubinsteyn,谢谢您的回答--我想使用cython,因为它目前是最便携和最广泛使用的。正如我在问题中所说的,是的,如果我反转两个循环,一切都很好,但是我必须为每个点遍历一次完整的数据数组,这似乎是一种浪费。我不明白为什么在这种情况下,减少工作的罚款,而不是在顺序,我有它上面的代码。嘿,韩国,对不起,我错过了你的问题的那一部分。问题归结为破坏性更新,如“sum_array3d[pi,0]+=weight_i*dx”。两个线程可以同时具有相同的pi值。它们都读取sum_array3d[0,0]。然后每个局部地增加该值,它们都写回它们的局部结果。第二个线程覆盖第一个线程的结果,因此您的竞争条件。在普通的C/C++代码中,我认为您可以使用“#pragma omp atomic”或锁定来修复正确性,但我不确定如何在Cython中实现这一点,并且无论如何它会破坏您的性能。嗨,alex,是的,通常这将是一个竞争条件,除了Cython-prange语句中应该将就地运算符转换为一个缩减。例如,从“线程局部性和归约自动推断变量。如果对变量使用内插运算符,它将成为归约。”我认为关键字是“变量”。这可能是我不熟悉的OpenMP规范的一个角落,但我相信给“#omp parallel reduce”pragma的变量必须是简单的本地名称,而不是数组中的位置。Cython可能只查找形式为“reduce\u var+=some\u val”的模式,而默默地忽略“reduce\u array[idx]+=some\u val”。是的,我刚刚尝试使用数组元素作为reduce变量,OpenMP barfs:“[”token“pragma omp parallel for reduce(+:sum[0]”之前的“:”test.c:42:error:expected'))^test.c:8:36:error:“sum”的“reduce”类型无效#pragma omp parallel for reduce(+:sum[0])“我怀疑Cython在这里会比他们的编译目标聪明得多。