Cython 0.15,用 OpenMP 並行多核加速 Python!

來源:互聯網
上載者:User

賴勇浩(http://laiyonghao.com)

註:
0、讀懂這篇文章需要瞭解 OpenMP 基本用法。
1、讀懂這篇文章需要瞭解 GIL 基本概念。
2、基本上是這篇的翻譯:http://docs.cython.org/src/userguide/parallelism.html,標題是我自己取的,如有錯漏、不明,敬請參詳原文。
3、本篇不是使用 cython.parallel 的指南(或手冊),僅作資訊傳播之用。
4、我之前翻譯過一篇文章《OpenMP與C++:事半功倍地獲得多線程的好處》有助於理解這篇文章,見:上(http://blog.csdn.net/lanphaday/article/details/1503817),下(http://blog.csdn.net/lanphaday/article/details/1507834)。

Cython 0.15 新增了 cython.parallel 模組,實現對原生並行編程的支援。現在只支援 OpenMP,以後會加入更多的後端支援。需要注意的是並行是運行在釋放了 GIL 的環境下的。

cython.parallel.prange([start], stop[, step], nogil=False, schedule=None)

此函數並行迴圈,OpenMP 自動構建線程池,並根據指定的調度方案指派作業給這些線程。step 參數不可為 0,如果 nogil 參數為 true,那麼這個迴圈就會被封裝在一個 nogil 環境中。shedule 參數支援 static/dynamic/guided/auto/runtime 等 OpenMP 中定義的調度機制。
thread-locality 和 reduction 是從變數進來推斷決定的。在 prange 塊中被賦值的變數,會被看作 lastprivate,意思是這個變數的值會是最後一次迭代的值。如果對變數使用了原地操作符,那它會被看作 reduction,意思是每條線程都拷貝了一個私人變數,然後在迴圈結束後應用這個操作符,並賦值給原來的變數。索引變數總是 lastprivate,而在並行塊中被賦值的變數都會被看作 private,而且在離開並行塊後不可用,因為無法確定它的最後的值。(譯註:對這兩段理解不能的話,需要閱讀 OpenMP 相關文檔)。
下面是一個關於 reduction 的例子:

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 i    for i in prange(x.shape[0]):        x[i] = alpha * x[i]

cython.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 loop    for idx in range(size):        local_buf[i] = 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 i    for i in prange(n, nogil=True):        if i == 8:            with gil:                raise Exception()        elif i == 4:            break        elif i == 2:            return i

上例中到底是拋出異常,還是簡單地 break 又或者返回 2,是沒有定義的(不確定的)。

嵌套並行

因為 gcc 的一個 bug,現在嵌套並行被禁用掉了,不過,你可以在一個並行段中調用含有並行段的函數。

參考資料

[1] http://www.openmp.org/mp-documents/spec30.pdf
[2] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49897

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.