from cython.parallel import prange, parallel, threadidcdef int icdef int sum = 0for i in prange(n, nogil=True):sum += iprint sum再来一个共享 numpy 数组的例子:
from cython.parallel import *def func(np.ndarray[double] x, double alpha):cdef Py_ssize_t ifor i in prange(x.shape[0]):x = alpha * xcython.parallel.parallel()
可以在 with 语句中使用这个指令来实现代码序列的并行执行。这在为 prange 准备 thread-local 的缓冲区时非常有用。内含的 prange 将成为不并行的工作共享循环,所以一切在并行 section 中被赋值的变量在 prange 中也是 private。所有并行块中的 private 变量在离开并行块后都不可用。
thread-local 缓冲的例子:
from cython.parallel import *from libc.stdlib cimport abort, malloc, freecdef Py_ssize_t idx, i, n = 100cdef int * local_bufcdef size_t size = 10with nogil, parallel():local_buf = <int *> malloc(sizeof(int) * size)if local_buf == NULL:abort()# populate our local buffer in a sequential loopfor idx in range(size):local_buf = i * 2# share the work using the thread-local buffer(s)for i in prange(n, schedule='guided'):func(local_buf)free(local_buf)以后 sections 将支持并行块,这样可以把 sections 的代码分配给多个线程执行。 cython.parallel.threadid()
返回线程 ID,对于 n 个线程,它们的 ID 范围是 [0, n)。
编译
要启用 OpenMP 支持,需要把 C 或 C++ 编译器的 OpenMP 开关打开,gcc 适用的 setup.py 如下:
from distutils.core import setupfrom distutils.extension import Extensionfrom Cython.Distutils import build_extext_module = Extension("hello",["hello.pyx"],extra_compile_args=['-fopenmp'],extra_link_args=['-fopenmp'],)setup(name = 'Hello world app',cmdclass = {'build_ext': build_ext},ext_modules = [ext_module],)打断
nogil 模式下的并行的 with 和 prange 块支持 break、continue 和 return。此外,还能够在这些块中使用 with gil 块,也可以抛出异常。但是,因为使用了 OpenMP,不能跳出了事,最好还是退出程序。以 prange() 为例,在第一次 return、break 或抛出异常后,所有线程的每一次循环都会跳过。所以如果有多个值应当返回时该返回哪个值是没有定义的,因为迭代本身是没有特定的顺序的:
from cython.parallel import prangecdef int func(Py_ssize_t n):cdef Py_ssize_t ifor i in prange(n, nogil=True):if i == 8:with gil:raise Exception()elif i == 4:breakelif i == 2:return i上例中到底是抛出异常,还是简单地 break 又或者返回 2,是没有定义的(不确定的)。 嵌套并行
因为 gcc 的一个 bug,现在嵌套并行被禁用掉了,不过,你可以在一个并行段中调用含有并行段的函数。