问题 cython中的cython共享内存.parallel.prange - block


我有一个功能 foo 它将一个指向内存的指针作为参数,并对该内存进行写入和读取:

cdef void foo (double *data):
   data[some_index_int] = some_value_double
   do_something_dependent_on (data)

我正在分配给 data 像这样:

cdef int N = some_int
cdef double *data = <double*> malloc (N * sizeof (double))

cdef int i
for i in cython.parallel.prange (N, nogil=True):
    foo (data)

readout (data)

我现在的问题是:不同的线程如何对待这个?我的猜测是指向的内存 data 将由所有线程共享,并在函数内部“同时”读取或写入 foo。这将使所有结果陷入混乱,因为人们不能依赖先前设定的数据值(在...内) foo)?我的猜测是正确的还是在cython编译器中实现了一些神奇的安全带?

非常感谢你提前。


1912
2017-09-25 10:30


起源



答案:


一个好方法是让主阵列位于线程之外。然后你给每个线程指向应该由线程计算的主数组部分的指针。

以下示例是矩阵乘法的实现(类似于 dot 对于二维阵列)其中:

c = a*b

这里的并行性是通过行来实现的 a。检查指针如何传递给 multiply 函数以便允许不同的线程共享相同的数组。

import numpy as np
cimport numpy as np
import cython
from cython.parallel import prange

ctypedef np.double_t cDOUBLE
DOUBLE = np.float64


def mydot(np.ndarray[cDOUBLE, ndim=2] a, np.ndarray[cDOUBLE, ndim=2] b):
    cdef np.ndarray[cDOUBLE, ndim=2] c
    cdef int i, M, N, K

    c = np.zeros((a.shape[0], b.shape[1]), dtype=DOUBLE)
    M = a.shape[0]
    N = a.shape[1]
    K = b.shape[1]

    for i in prange(M, nogil=True):
        multiply(&a[i,0], &b[0,0], &c[i,0], N, K)

    return c


@cython.wraparound(False)
@cython.boundscheck(False)
@cython.nonecheck(False)
cdef void multiply(double *a, double *b, double *c, int N, int K) nogil:
    cdef int j, k
    for j in range(N):
        for k in range(K):
            c[k] += a[j]*b[k+j*K]

要检查您是否可以使用此脚本:

import time

import numpy as np

import _stack

a = np.random.random((10000,500))
b = np.random.random((500,2000))

t = time.clock()
c = np.dot(a, b)
print('finished dot: {} s'.format(time.clock()-t))

t = time.clock()
c2 = _stack.mydot(a, b)
print('finished mydot: {} s'.format(time.clock()-t))

print 'Passed test:', np.allclose(c, c2)

在我的电脑上它给出了:

finished dot: 0.601547366526 s
finished mydot: 2.834147917 s
Passed test: True

如果行数 a 小于cols数或cols数 b 该 mydot 会更糟糕,需要更好地检查哪个维度来实现并行性。


8
2017-12-11 13:21





我假设没有读或写同步锁 data 线程将读/写内存位置并覆盖彼此的更改。没有某种同步,您将无法获得一致的结果。

虽然文档(http://docs.cython.org/src/userguide/parallelism.html)似乎暗示OpenMP(默认后端)自动创建线程本地。


2
2017-12-10 14:07



注意: 我的任何系统都没有OpenMP,因此无法轻松测试。 - James Mills


答案:


一个好方法是让主阵列位于线程之外。然后你给每个线程指向应该由线程计算的主数组部分的指针。

以下示例是矩阵乘法的实现(类似于 dot 对于二维阵列)其中:

c = a*b

这里的并行性是通过行来实现的 a。检查指针如何传递给 multiply 函数以便允许不同的线程共享相同的数组。

import numpy as np
cimport numpy as np
import cython
from cython.parallel import prange

ctypedef np.double_t cDOUBLE
DOUBLE = np.float64


def mydot(np.ndarray[cDOUBLE, ndim=2] a, np.ndarray[cDOUBLE, ndim=2] b):
    cdef np.ndarray[cDOUBLE, ndim=2] c
    cdef int i, M, N, K

    c = np.zeros((a.shape[0], b.shape[1]), dtype=DOUBLE)
    M = a.shape[0]
    N = a.shape[1]
    K = b.shape[1]

    for i in prange(M, nogil=True):
        multiply(&a[i,0], &b[0,0], &c[i,0], N, K)

    return c


@cython.wraparound(False)
@cython.boundscheck(False)
@cython.nonecheck(False)
cdef void multiply(double *a, double *b, double *c, int N, int K) nogil:
    cdef int j, k
    for j in range(N):
        for k in range(K):
            c[k] += a[j]*b[k+j*K]

要检查您是否可以使用此脚本:

import time

import numpy as np

import _stack

a = np.random.random((10000,500))
b = np.random.random((500,2000))

t = time.clock()
c = np.dot(a, b)
print('finished dot: {} s'.format(time.clock()-t))

t = time.clock()
c2 = _stack.mydot(a, b)
print('finished mydot: {} s'.format(time.clock()-t))

print 'Passed test:', np.allclose(c, c2)

在我的电脑上它给出了:

finished dot: 0.601547366526 s
finished mydot: 2.834147917 s
Passed test: True

如果行数 a 小于cols数或cols数 b 该 mydot 会更糟糕,需要更好地检查哪个维度来实现并行性。


8
2017-12-11 13:21





我假设没有读或写同步锁 data 线程将读/写内存位置并覆盖彼此的更改。没有某种同步,您将无法获得一致的结果。

虽然文档(http://docs.cython.org/src/userguide/parallelism.html)似乎暗示OpenMP(默认后端)自动创建线程本地。


2
2017-12-10 14:07



注意: 我的任何系统都没有OpenMP,因此无法轻松测试。 - James Mills