Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/315.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 这个问题可以用OpenMP在Cython中并行实现吗?_Python_Parallel Processing_Openmp_Cython - Fatal编程技术网

Python 这个问题可以用OpenMP在Cython中并行实现吗?

Python 这个问题可以用OpenMP在Cython中并行实现吗?,python,parallel-processing,openmp,cython,Python,Parallel Processing,Openmp,Cython,我用OpenMP对一些Cython代码进行了视差分析。有时,代码会计算错误的结果 我为我的问题创建了一个几乎最小的工作示例。几乎是这样,因为错误结果的频率似乎取决于代码中哪怕是最微小的更改,因此,例如,我保留了函数指针 Cython代码是 #cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True # distutils: language = c++ import numpy as np ci

我用OpenMP对一些Cython代码进行了视差分析。有时,代码会计算错误的结果

我为我的问题创建了一个几乎最小的工作示例。几乎是这样,因为错误结果的频率似乎取决于代码中哪怕是最微小的更改,因此,例如,我保留了函数指针

Cython代码是

#cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True
# distutils: language = c++

import numpy as np

cimport cython
from cython.parallel import prange, parallel
from libcpp.vector cimport vector
cimport numpy as np

cdef inline double estimator_matheron(const double f_diff) nogil:
    return f_diff * f_diff

ctypedef double (*_estimator_func)(const double) nogil

cdef inline void normalization_matheron(
    vector[double]& variogram,
    vector[long]& counts,
    const int variogram_len
):
    cdef int i
    for i in range(variogram_len):
        if counts[i] == 0:
            counts[i] = 1
        variogram[i] /= (2. * counts[i])

ctypedef void (*_normalization_func)(vector[double]&, vector[long]&, const int)


def test(const double[:] f):
    cdef _estimator_func estimator_func = estimator_matheron
    cdef _normalization_func normalization_func = normalization_matheron

    cdef int i_max = f.shape[0] - 1
    cdef int j_max = i_max + 1

    cdef vector[double] variogram_local, variogram
    cdef vector[long] counts_local, counts

    cdef int i, j

    with nogil, parallel():
        variogram_local.resize(j_max, 0.0)
        counts_local.resize(j_max, 0)

        for i in range(i_max):
            for j in range(1, j_max-i):
                counts_local[j] += 1
                variogram_local[j] += estimator_func(f[i] - f[i+j])

    normalization_func(variogram_local, counts_local, j_max)

    return np.asarray(variogram_local)
为了测试代码,我使用了以下脚本:

import numpy as np
from cython_parallel import test

z = np.array(
    (41.2, 40.2, 39.7, 39.2, 40.1, 38.3, 39.1, 40.0, 41.1, 40.3),
    dtype=np.double,
)

print(test(z))
结果应该是

[0.         0.49166667 0.7625     1.09071429 0.90166667 1.336
 0.9525     0.435      0.005      0.405     ]
这就是错误结果通常的样子

[0.         0.44319444 0.75483871 1.09053571 0.90166667 1.336
 0.9525     0.435      0.005      0.405     ]
此代码主要将数字汇总到向量变异函数中。大多数情况下,这段代码是有效的,但如果没有足够的统计数据,可能每30次都会产生错误的结果。如果我用nogil将行更改为parallel:to with nogil:,它总是有效的。如果我根本不使用函数指针,它也总是有效的,如下所示:

    with nogil, parallel():
        variogram_local.resize(j_max, 0.0)
        counts_local.resize(j_max, 0)

        for i in range(i_max):
            for j in range(1, j_max-i):
                counts_local[j] += 1
                variogram_local[j] += (f[i] - f[i+j]) * (f[i] - f[i+j])

    for j in range(j_max):
        if counts_local[j] == 0:
            counts_local[j] = 1
        variogram_local[j] /= (2. * counts_local[j])

    return np.asarray(variogram_local)
variogram.resize(j_max, 0.0)
counts.resize(j_max, 0)

with nogil, parallel():
    for i in range(i_max):
        for j in prange(1, j_max-i):
            counts[j] += 1
            variogram[j] += estimator_func(f[i] - f[i+j])
完整的代码在不同的平台上测试,这些问题主要发生在带有clang的MacOS上,例如:

编辑

多亏了您的输入,我修改了代码,使用num_threads=2,它就可以工作了。但是一旦num_threads>2,我就会再次得到错误的结果。你认为,如果Cython对OpenMP的支持是完美的,我的新代码应该可以工作,还是我仍然有问题? 如果这应该在Cython的一边,我想我会在纯C++中实现代码。
def test(const double[:] f):
    cdef int i_max = f.shape[0] - 1
    cdef int j_max = i_max + 1

    cdef vector[double] variogram_local, variogram
    cdef vector[long] counts_local, counts

    cdef int i, j, k

    variogram.resize(j_max, 0.0)
    counts.resize(j_max, 0)

    with nogil, parallel(num_threads=2):
        variogram_local = vector[double](j_max, 0.0)
        counts_local = vector[long)(j_max, 0)

        for i in prange(i_max):
            for j in range(1, j_max-i):
                counts_local[j] += 1
                variogram_local[j] += (f[i] - f[i+j]) * (f[i] - f[i+j])

        for k in range(j_max):
            counts[k] += counts_local[k]
            variogram[k] += variogram_local[k]

    for i in range(j_max):
        if counts[i] == 0:
            counts[i] = 1
        variogram[i] /= (2. * counts[i])

    return np.asarray(variogram)
与它们的名字相反,变异函数和计数实际上并不是局部的。它们是共享的,所有线程都并行地处理它们,因此结果是未定义的

请注意,您实际上没有共享任何工作。它只是所有线程都在做相同的事情——整个串行任务

一个合理的并行版本看起来更像这样:

    with nogil, parallel():
        variogram_local.resize(j_max, 0.0)
        counts_local.resize(j_max, 0)

        for i in range(i_max):
            for j in range(1, j_max-i):
                counts_local[j] += 1
                variogram_local[j] += (f[i] - f[i+j]) * (f[i] - f[i+j])

    for j in range(j_max):
        if counts_local[j] == 0:
            counts_local[j] = 1
        variogram_local[j] /= (2. * counts_local[j])

    return np.asarray(variogram_local)
variogram.resize(j_max, 0.0)
counts.resize(j_max, 0)

with nogil, parallel():
    for i in range(i_max):
        for j in prange(1, j_max-i):
            counts[j] += 1
            variogram[j] += estimator_func(f[i] - f[i+j])
共享数组在外部初始化,然后线程共享内部j循环。因为没有两个线程在同一个j上工作,所以这样做是安全的

现在,将内部循环并行化可能并不理想。如果要实际并行化外部循环,实际上必须生成实际的局部变量,然后合并/减少它们。

与它们的名称相反,变异函数和计数实际上不是局部的。它们是共享的,所有线程都并行地处理它们,因此结果是未定义的

请注意,您实际上没有共享任何工作。它只是所有线程都在做相同的事情——整个串行任务

一个合理的并行版本看起来更像这样:

    with nogil, parallel():
        variogram_local.resize(j_max, 0.0)
        counts_local.resize(j_max, 0)

        for i in range(i_max):
            for j in range(1, j_max-i):
                counts_local[j] += 1
                variogram_local[j] += (f[i] - f[i+j]) * (f[i] - f[i+j])

    for j in range(j_max):
        if counts_local[j] == 0:
            counts_local[j] = 1
        variogram_local[j] /= (2. * counts_local[j])

    return np.asarray(variogram_local)
variogram.resize(j_max, 0.0)
counts.resize(j_max, 0)

with nogil, parallel():
    for i in range(i_max):
        for j in prange(1, j_max-i):
            counts[j] += 1
            variogram[j] += estimator_func(f[i] - f[i+j])
共享数组在外部初始化,然后线程共享内部j循环。因为没有两个线程在同一个j上工作,所以这样做是安全的


现在,将内部循环并行化可能并不理想。如果要实际并行化外循环,实际上必须生成实际的局部变量,然后合并/减少它们。

修改后的代码的问题是,您有一个竞争条件,该部分将计数_local和变异函数_local相加。您希望在并行块中使用它,这样您仍然可以访问线程局部变量,但一次只需要一个线程来处理它。最简单的方法是将其放入with gil:块中,以便Python一次执行一个线程:

with gil:
    for k in range(j_max):
        counts[k] += counts_local[k]
        variogram[k] += variogram_local[k]
这一点在最后应该是一个快速的任务,所以不应该花费太长时间

如果是在C/C++中,您可能会对块使用pragma openmp-atomic或pragma openmp-critical。在Cython中很难做到这一点,因为他们的OpenMP支持非常基本,但您可能会滥用包装的C宏使添加原子化


Cython的OpenMP支持实际上是围绕简单循环和标量缩减而设计的。如果你做的比这还多,那么它就没有语法来控制OpenMP,因此我倾向于建议你在C或C++中写你最喜欢的OpenMP函数,不管你用哪个更舒服。

< p>你的修改代码的问题是你有一个竞争条件,这个部分加上了CuthSux本地和局部变异函数。您希望在并行块中使用它,这样您仍然可以访问线程局部变量,但一次只需要一个线程来处理它。最简单的方法是将其放入with gil:块中,以便Python一次执行一个线程:

with gil:
    for k in range(j_max):
        counts[k] += counts_local[k]
        variogram[k] += variogram_local[k]
这一点在最后应该是一个快速的任务,所以不应该花费太长时间

如果是在C/C++中,您可能会对块使用pragma openmp-atomic或pragma openmp-critical。在Cython中很难做到这一点,因为他们的OpenMP支持非常基本,但您可能会滥用包装的C宏使添加原子化

Cython的OpenMP支持实际上是围绕简单循环和标量缩减而设计的。如果你做的比这还多,那么它就没有语法来给OpenMP提供精细的控制,因此,我倾向于推荐你在C或C++中编写你的关键OpenMP函数,不管你是否更舒服。
能够。

非常感谢您非常清楚的回答。天真地说,我一直认为OpenMP的并行化很容易。从我读到的资料来看,目前在Cython中甚至不可能以这种方式使用局部变量。你有什么进一步的提示吗?我怀疑如果你在并行块内分配到*_local,例如变差函数_local=vector[double]j_max,它将变成线程本地。然而,由于Cython隐式地做出这些决定,因此如果不查看生成的代码,就很难知道它做了什么。通常最好是在C/C++中编写并行循环,您可以自己控制这些东西,然后从cythonasignments调用它,事实上,在并行块中使变量成为局部线程。但是,您需要考虑这些变量在并行块之后是不可用的。非常感谢您的非常明确的答案。天真地说,我一直认为OpenMP的并行化很容易。从我读到的资料来看,目前在Cython中甚至不可能以这种方式使用局部变量。你有什么进一步的提示吗?我怀疑如果你在并行块内分配到*_local,例如变差函数_local=vector[double]j_max,它将变成线程本地。然而,由于Cython隐式地做出这些决定,因此如果不查看生成的代码,就很难知道它做了什么。通常最好是在C/C++中编写并行循环,您可以自己控制这些东西,然后从cythonasignments调用它,事实上,在并行块中使变量成为局部线程。但是,你需要考虑这些变量在并行块之后是不可用的。此外,@ Zulan所说的,在平行部分之后,用**局部变量调用正规化函数,所以如果它们是适当的局部,那么不管怎样,你都会得到一个胡说八道的结果。在并行部分之后调用normalization_func,但是使用*_局部变量,因此如果它们是适当的局部变量,那么无论如何都会得到一个无意义的结果