Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/357.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Cython并行循环问题_Python_Performance_Openmp_Cython - Fatal编程技术网

Python Cython并行循环问题

Python Cython并行循环问题,python,performance,openmp,cython,Python,Performance,Openmp,Cython,我使用cython计算成对距离矩阵,使用自定义度量作为快速替代 我的动机 我的度量具有以下形式 def mymetric(u,v,w): np.sum(w * (1 - np.abs(np.abs(u - v) / np.pi - 1))**2) 使用scipy的成对距离可以计算为 x = sp.spatial.distance.pdist(r, metric=lambda u, v: mymetric(u, v, w)) 这里,r是维度为n的m-by-n向量的m矩阵,w是一个具有

我使用cython计算成对距离矩阵,使用自定义度量作为快速替代

我的动机 我的度量具有以下形式

def mymetric(u,v,w):
     np.sum(w * (1 - np.abs(np.abs(u - v) / np.pi - 1))**2)
使用scipy的成对距离可以计算为

x = sp.spatial.distance.pdist(r, metric=lambda u, v: mymetric(u, v, w))
这里,
r
是维度为
n
m
-by-
n
向量的
m
矩阵,
w
是一个具有二次张力的“权重”因子
n

因为在我的问题中,
m
相当高,所以计算速度非常慢。对于
m=2000
n=10
这大约需要20秒

Cython初解 我在cython中实现了一个简单的函数来计算成对距离,并立即得到了非常有希望的结果——加速比超过500倍

import numpy as np
cimport numpy as np
import cython

from libc.math cimport fabs, M_PI

@cython.wraparound(False)
@cython.boundscheck(False)
def pairwise_distance(np.ndarray[np.double_t, ndim=2] r, np.ndarray[np.double_t, ndim=1] w):
    cdef int i, j, k, c, size
    cdef np.ndarray[np.double_t, ndim=1] ans
    size = r.shape[0] * (r.shape[0] - 1) / 2
    ans = np.zeros(size, dtype=r.dtype)
    c = -1
    for i in range(r.shape[0]):
        for j in range(i + 1, r.shape[0]):
            c += 1
            for k in range(r.shape[1]):
                ans[c] += w[k] * (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))**2.0

    return ans
使用OpenMP时出现的问题 我想使用OpenMP进一步加快计算速度,但是,下面的解决方案大约比串行版本慢3倍

import numpy as np
cimport numpy as np

import cython
from cython.parallel import prange, parallel

cimport openmp

from libc.math cimport fabs, M_PI

@cython.wraparound(False)
@cython.boundscheck(False)
def pairwise_distance_omp(np.ndarray[np.double_t, ndim=2] r, np.ndarray[np.double_t, ndim=1] w):
    cdef int i, j, k, c, size, m, n
    cdef np.double_t a
    cdef np.ndarray[np.double_t, ndim=1] ans
    m = r.shape[0]
    n = r.shape[1]
    size = m * (m - 1) / 2
    ans = np.zeros(size, dtype=r.dtype)
    with nogil, parallel(num_threads=8):
        for i in prange(m, schedule='dynamic'):
            for j in range(i + 1, m):
                c = i * (m - 1) - i * (i + 1) / 2 + j - 1
                for k in range(n):
                    ans[c] += w[k] * (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))**2.0

    return ans
我不知道为什么它实际上变慢了,但我尝试引入以下更改。这不仅导致性能稍差,而且结果距离
ans
仅在数组开头正确计算,其余仅为零。通过这种方式实现的加速可以忽略不计

import numpy as np
cimport numpy as np

import cython
from cython.parallel import prange, parallel

cimport openmp

from libc.math cimport fabs, M_PI
from libc.stdlib cimport malloc, free

@cython.wraparound(False)
@cython.boundscheck(False)
def pairwise_distance_omp_2(np.ndarray[np.double_t, ndim=2] r, np.ndarray[np.double_t, ndim=1] w):
    cdef int k, l, c, m, n
    cdef Py_ssize_t i, j, d
    cdef size_t size
    cdef int *ci, *cj

    cdef np.ndarray[np.double_t, ndim=1, mode="c"] ans

    cdef np.ndarray[np.double_t, ndim=2, mode="c"] data
    cdef np.ndarray[np.double_t, ndim=1, mode="c"] weight

    data = np.ascontiguousarray(r, dtype=np.float64)
    weight = np.ascontiguousarray(w, dtype=np.float64)

    m = r.shape[0]
    n = r.shape[1]
    size = m * (m - 1) / 2
    ans = np.zeros(size, dtype=r.dtype)

    cj = <int*> malloc(size * sizeof(int))
    ci = <int*> malloc(size * sizeof(int))

    c = -1
    for i in range(m):
        for j in range(i + 1, m):
            c += 1
            ci[c] = i
            cj[c] = j

    with nogil, parallel(num_threads=8):
        for d in prange(size, schedule='guided'):
            for k in range(n):
                ans[d] += weight[k] * (1.0 - fabs(fabs(data[ci[d], k] - data[cj[d], k]) / M_PI - 1.0))**2.0

    return ans
总结 我对cython没有任何经验,只知道C语言的基本知识。如果能给我一些建议,说明这种意外行为的原因,甚至是如何更好地重新表述我的问题,我将不胜感激


最佳串行解决方案(比原始串行解决方案快10%) 最佳并行解决方案(使用8个线程,比原始并行快1%,比最佳串行快6倍)
未解决的问题: 当我尝试应用回答中提出的
累加器
解决方案时,我得到以下错误:

Error compiling Cython file:
------------------------------------------------------------
...
                c = i * (m - 1) - i * (i + 1) / 2 + j - 1
                accumulator = 0
                for k in range(n):
                    tmp = (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))
                    accumulator += w[k] * (tmp*tmp)
                ans[c] = accumulator
                                   ^
------------------------------------------------------------
pdist.pyx:207:36: Cannot read reduction variable in loop body
完整代码:

@cython.cdivision(True)
@cython.wraparound(False)
@cython.boundscheck(False)
def pairwise_distance_omp(np.ndarray[np.double_t, ndim=2] r, np.ndarray[np.double_t, ndim=1] w):
    cdef int i, j, k, c, size, m, n
    cdef np.ndarray[np.double_t, ndim=1] ans
    cdef np.double_t accumulator, tmp
    m = r.shape[0]
    n = r.shape[1]
    size = m * (m - 1) / 2
    ans = np.zeros(size, dtype=r.dtype)
    with nogil, parallel(num_threads=8):
        for i in prange(m, schedule='dynamic'):
            for j in range(i + 1, m):
                c = i * (m - 1) - i * (i + 1) / 2 + j - 1
                accumulator = 0
                for k in range(n):
                    tmp = (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))
                    accumulator += w[k] * (tmp*tmp)
                ans[c] = accumulator

    return ans

我自己没有计时,因此这可能不会有太大帮助,但是:

如果运行
cython-a
获得初始尝试的注释版本(
pairwise\u distance\u omp
),您会发现
ans[c]+=…
行是黄色的,表明它有Python开销。看一下,对应于这条线的C表示它正在检查是否被零除。其中一个关键部分是:

if (unlikely(M_PI == 0)) {
您知道这永远不会是真的(而且在任何情况下,您可能会接受NaN值,而不是异常值)。您可以通过向函数添加以下额外的装饰器来避免此检查:

@cython.cdivision(True)
# other decorators
def pairwise_distance_omp # etc...
这将减少相当多的C代码,包括必须在单个线程中运行的代码。另一方面,这些代码中的大部分都不应该运行,编译器应该能够解决这一问题,因此目前还不清楚这会产生多大的影响


第二个建议:

# at the top
cdef np.double_t accumulator, tmp

    # further down later in the loop:
    c = i * (m - 1) - i * (i + 1) / 2 + j - 1
    accumulator = 0
    for k in range(r.shape[1]):
        tmp = (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))
        accumulator = accumulator + w[k] * (tmp*tmp)
    ans[c] = accumulator

这有两个优点:1)
tmp*tmp
可能比浮点指数快到2的幂。2) 避免读取
ans
数组,这可能会有点慢,因为编译器必须始终小心其他线程没有更改它(即使您知道它不应该更改)。

这当然有帮助,谢谢,但是问题仍然存在。这两个OpenMP版本现在大约比串行版本慢2倍(在8个物理内核中使用8个线程)。另外,我仍然不知道为什么第二次尝试只计算部分结果。@Ondrian我添加了第二个建议,我认为这可能会有所帮助(尽管其中一个改进也可以应用于非并行版本)。我还没有看过第二次尝试。第二个建议将串行代码的速度提高了10%,这非常好。我也设法在并行代码中应用了
tmp*tmp
部分,但是,
累加器部分仍然出现错误。顺便说一下,奇怪的初始计时(串行代码比并行代码快)实际上是我测量的结果!显然,
time.process\u time()
不是一个好方法。对此感到尴尬。:)我现在接受答案,但如果你知道如何处理最后一个错误,我将不胜感激。如果您不这样做,我可能会创建一个新问题。
acculator=acculator+w[k]*(tmp*tmp)
适合我。它感到困惑,认为它被用作OpenMP缩减(例如),但以不同的方式编写它会有所帮助。我应该在发布它之前测试它真的…这很有效!对于8个线程,大约比同等串行速度快6.5倍。非常感谢!:)更新:在我的代码中发现一个错误,当前的问题只是并行代码的性能。Ondrian-当我尝试使用累加器对矩阵列进行简单求和时,我得到了相同的“无法读取循环体中的缩减变量”错误-是什么错误导致了这个错误?@aph问题是,如果我没记错的话,使用
acculator+=something
语法而不是
acculator=acculator+something
。谢谢@Ondrian-这正是我的问题!
if (unlikely(M_PI == 0)) {
@cython.cdivision(True)
# other decorators
def pairwise_distance_omp # etc...
# at the top
cdef np.double_t accumulator, tmp

    # further down later in the loop:
    c = i * (m - 1) - i * (i + 1) / 2 + j - 1
    accumulator = 0
    for k in range(r.shape[1]):
        tmp = (1.0 - fabs(fabs(r[i, k] - r[j, k]) / M_PI - 1.0))
        accumulator = accumulator + w[k] * (tmp*tmp)
    ans[c] = accumulator