欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Cython中prange函數(shù)實現(xiàn)for循環(huán)的并行

 更新時間:2022年08月01日 09:38:21   作者:古明地覺  
Cython中提供了一個prange函數(shù),專門用于循環(huán)的并行執(zhí)行。這個 prange的特殊功能是Cython獨一無二的,并且prange只能與for循環(huán)搭配使用,不能獨立存在。本文就將使用 prange 實現(xiàn) for 循環(huán)的并行,感興趣的可以了解一下

楔子

上一篇文章我們探討了 GIL 的原理,以及如何釋放 GIL 實現(xiàn)并行,做法是將函數(shù)聲明為 nogil,然后使用 with nogil 上下文管理器即可。在使用上非常簡單,但如果我們想讓循環(huán)也能夠并行執(zhí)行,那么該方式就不太方便了,為此 Cython 提供了一個 prange 函數(shù),專門用于循環(huán)的并行執(zhí)行。

這個 prange 的特殊功能是 Cython 獨一無二的,并且 prange 只能與 for 循環(huán)搭配使用,不能獨立存在。

Cython 使用 OpenMP API 實現(xiàn) prange,用于多平臺共享內(nèi)存的處理。但 OpenMP 需要 C 或者 C++ 編譯器支持,并且編譯時需要指定特定的編譯參數(shù)來啟動。例如:當(dāng)我們使用 gcc 時,必須在編譯和鏈接二進制文件的時候指定一個 -fopenmp,以確保啟用 OpenMP。

許多編譯器均支持 OpenMP ,包括免費的和商業(yè)的。但 Clang/LLVM 則是一個最顯著的例外,它只在一個單獨的分支中得到了初步的支持,而為它完全實現(xiàn)的 OpenMP 還在開發(fā)當(dāng)中。

而使用 prange,需要從 cython.parallel 中進行導(dǎo)入。但是在這之前,我們先來看一個例子:

import?numpy?as?np
from?cython?cimport?boundscheck,?wraparound

cdef?inline?double?norm2(double?complex?z)?nogil:
????"""
????接收一個復(fù)數(shù)?z,?計算它的模的平方
????由于 norm2 要被下面的?escape?函數(shù)多次調(diào)用
????這里通過?inline?聲明成內(nèi)聯(lián)函數(shù)
????:param?z:?
????:return:?
????"""
????return?z.real?*?z.real?+?z.imag?*?z.imag


cdef?int?escape(double?complex?z,
????????????????double?complex?c,
????????????????double?z_max,
????????????????int?n_max)?nogil:
????"""
????這個函數(shù)具體做什么,?不是我們的重點
????我們不需要關(guān)心
????"""
????cdef:
????????int?i?=?0
????????double?z_max2?=?z_max?*?z_max
????while?norm2(z)?<?z_max2?and?i?<?n_max:
????????z?=?z?*?z?+?c
????????i?+=?1
????return?i


@boundscheck(False)
@wraparound(False)
def?calc_julia(int?resolution,
???????????????double?complex?c,
???????????????double?bound=1.5,
???????????????double?z_max=4.0,
???????????????int?n_max=1000):
????"""
????我們將要在?Python?中調(diào)用的函數(shù)
????"""
????cdef:
????????double?step?=?2.0?*?bound?/?resolution
????????int?i,?j
????????double?complex?z
????????double?real,?imag
????????int[:,?::?1]?counts
????counts?=?np.zeros((resolution?+?1,?resolution?+?1),?dtype="int32")

????for?i?in?range(resolution?+?1):
????????real?=?-bound?+?i?*?step
????????for?j?in?range(resolution?+?1):
????????????imag?=?-bound?+?j?*?step
????????????z?=?real?+?imag?*?1j
????????????counts[i,?j]?=?escape(z,?c,?z_max,?n_max)

????return?np.array(counts,?copy=False)

我們手動編譯一下,然后調(diào)用 calc_julia 函數(shù),這個函數(shù)做什么不需要關(guān)心,我們只需要將注意力放在那兩層 for 循環(huán)(準(zhǔn)確的說是外層循環(huán))上即可,這里我們采用手動編譯的形式。

import?cython_test
import?numpy?as?np
import?matplotlib.pyplot?as?plt
arr?=?cython_test.calc_julia(1000,?0.322?+?0.05j)
plt.imshow(np.log(arr))
plt.show()

那么 calc_julia 這個函數(shù)耗時多少呢?我們來測試一下:

使用 prange

對于上面的代碼來說,外層循環(huán)里面的邏輯是彼此獨立的,即當(dāng)前循環(huán)不依賴上一層循環(huán)的結(jié)果,因此這非常適合并行執(zhí)行。所以 prange 便閃亮登場了,我們只需要做簡單的修改即可:

import?numpy?as np
from?cython?cimport boundscheck,?wraparound
from?cython.parallel?cimport prange

cdef?inline?double?norm2(double?complex?z)?nogil:
????return?z.real?*?z.real?+?z.imag?*?z.imag


cdef?int?escape(double?complex?z,
????????????????double?complex?c,
????????????????double?z_max,
????????????????int?n_max)?nogil:
????cdef:
????????int?i?=?0
????????double?z_max2?=?z_max?*?z_max
????while?norm2(z)?<?z_max2?and?i?<?n_max:
????????z?=?z?*?z?+?c
????????i?+=?1
????return?i


@boundscheck(False)
@wraparound(False)
def?calc_julia(int?resolution,
???????????????double?complex?c,
???????????????double?bound=1.5,
???????????????double?z_max=4.0,
???????????????int?n_max=1000):
????cdef:
????????double?step?=?2.0?*?bound?/?resolution
????????int?i,?j
????????double?complex?z
????????double?real,?imag
????????int[:,?::?1]?counts
????counts?=?np.zeros((resolution?+?1,?resolution?+?1),?dtype="int32")
    #?只需要將外層的 range 換成 prange
????for?i?in?prange(resolution?+?1, nogil=True):
????????real?=?-bound?+?i?*?step
????????for?j?in?range(resolution?+?1):
????????????imag?=?-bound?+?j?*?step
????????????z?=?real?+?imag?*?1j
????????????counts[i,?j]?=?escape(z,?c,?z_max,?n_max)

????return?np.array(counts,?copy=False)

我們只需要將外層循環(huán)的 range 換成 prange 即可,里面指定 nogil=True,便可實現(xiàn)并行的效果,至于這個函數(shù)的其它參數(shù)以及用法后面會說。而且一旦使用了 prange,那么在編譯的時候,必須啟用 OpenMP,下面看一下編譯腳本。

from?distutils.core?import?setup,?Extension
from?Cython.Build?import?cythonize

ext?=?[Extension("cython_test",
?????????????????sources=["cython_test.pyx"],
?????????????????extra_compile_args=["-fopenmp"],
?????????????????extra_link_args=["-fopenmp"])]

setup(ext_modules=cythonize(ext,?language_level=3))

編譯測試一下:

我們看到效率大概是提升了兩倍,因為我 Windows 上使用的不是 gcc,所以這里是在 CentOS 上演示的。而我的 CentOS 服務(wù)器只有兩個核,因此效率提升大概兩倍左右。

所以只是做了一些非常簡單的修改,便可帶來如此巨大的性能提升,簡直妙啊。prange 是要搭配 for 循環(huán)來使用的,如果 for 循環(huán)內(nèi)部的邏輯彼此獨立,即第二層循環(huán)不依賴第一層循環(huán)的某些結(jié)果,那么不妨使用 prange 吧。

注意還沒完,我們還能做得更好,下面就來看看 prange 里面的其它的參數(shù),這樣我們能更好利用 prange 的并行特性。

prange 的其它參數(shù)

prange 函數(shù)的原型如下:

#?第一個參數(shù)?self?我們不需要管
#?prange?實際上是類?CythonDotParallel?的成員函數(shù)
#?因為?Cython?內(nèi)部執(zhí)行了下面這行邏輯
#?sys.modules['cython.parallel']?=?CythonDotParallel()
#?所以它將一個實例對象變成了一個模塊

def?prange(self,?start=0,?stop=None,?step=1,?
???????????nogil=False,?schedule=None,?
???????????chunksize=None,?num_threads=None):

我們先來看前三個參數(shù),start、stop、step。

  • prange(3): 相當(dāng)于 start=0、stop=3;
  • prange(1, 3): 相當(dāng)于 start=1、stop=3;
  • prange(1, 3, 2): 相當(dāng)于 start=1、stop=3、step=2;

類似于 range,同樣不包含結(jié)尾 stop。

然后是第四個參數(shù) nogil,它默認是 False,但事實上我們必須將其設(shè)置為 True,否則會報出編譯錯誤。

然后剩下的三個參數(shù),如果我們不指定的話,那么 Cython 編譯器采取的策略是將整個循環(huán)分成多個大小相同的連續(xù)塊,然后給每一個可用線程一個塊。然而這個策略實際上并不是最好的,因為每一層循環(huán)用的時間不一定一樣,如果一個線程很快就完成了,那么不就造成資源上的浪費了嗎?

我們修改一下,將 schedule 指定為 "static",chunksize 指定為 1:

for?i?in?prange(resolution?+?1,?nogil=True,?
????????????????schedule="static",?chunksize=1):

其它地方不變,只是加兩個參數(shù),然后重新測試一下。

我們看到效率上是差不多的,原因是我的機器只有兩個核,如果核數(shù)再多一些的話,那么速度就會明顯地提升。

下面來解釋一下剩余的三個參數(shù)的含義,首先是 schedule,它有以下幾個選項:

"static"

整個循環(huán)在編譯時會以一種固定的方式分配給多個線程,如果 chunksize 沒有指定,那么會分成 num_threads 個連續(xù)塊,一個線程一個塊。如果指定了 chunksize,那么每一塊會以輪詢調(diào)度算法(Round Robin)交給線程進行處理,適用于任務(wù)均勻分布的情況。

"dynamic"

線程在運行時動態(tài)地向調(diào)度器申請下一個塊,chunksize 默認為 1,當(dāng)任務(wù)負載不均時,動態(tài)調(diào)度是最佳的選擇。

"guided"

塊是動態(tài)分布的,就像 dynamic 一樣,但這與 dynamic 還不同,chunksize 的比例不是固定的,而是和 剩余迭代次數(shù) / 線程數(shù) 成比例關(guān)系。

"runtime"

不常用。

控制 schedule 和 chunksize 可以方便地探索不同的并行執(zhí)行策略、以及工作負載分配,通常指定 schedule 為 "static",加上設(shè)置一個合適的 chunksize 是最好的選擇。而 dynamic 和 guided 適用于動態(tài)變化的執(zhí)行上下文,但會導(dǎo)致運行時開銷。

當(dāng)然還有最后一個參數(shù) num_threads,很明顯不需要解釋,就是使用的線程數(shù)量。如果不指定,那么 prange 會使用盡可能多的線程。所以我們只是做了一點修改,便可以帶來巨大的性能提升,這種性能提升與 Cython 在純 Python 上帶來的性能提升成倍增關(guān)系。

在reductions操作上使用prange

我們經(jīng)常會循環(huán)遍歷數(shù)組計算它們的累和、累積等等,這種數(shù)據(jù)量減少的操作我們稱之為 reduction 操作。而 prange 對這樣的操作也是支持并行執(zhí)行的,我們舉個例子:

from?cython?cimport boundscheck,?wraparound

@boundscheck(False)
@wraparound(False)
def calc_julia(int?[:,?::?1]?counts,
???????????????int?val):
????cdef:
????????int?total?=?0
????????int?i,?j,?M,?N
????N,?M?=?counts.shape[:?2]
????for?i?in?range(M):
????????for?j?in?range(N):
????????????if?counts[i,?j]?==?val:
????????????????total?+=?1

????return?total?/?float(counts.size)

顯然我們是希望計算一個數(shù)組中值 val 的元素的個數(shù),下面測試一下:

如果改成 prange 的話,會有什么效果呢?代碼的其它部分不變,只需要導(dǎo)入 prange,然后將 range(M) 改成 prange(M, nogil=True) 即可。

速度比原來快了兩倍多,還是很可觀的,如果你的 CPU 是多核的,那么效率提升會更明顯。

這里我們沒有使用 schedule 和 chunksize 參數(shù),你也可以加上去。當(dāng)然啦,如果占用內(nèi)存過大的話,它可能無法像預(yù)期的一樣顯著地提升性能,因為 prange 的優(yōu)化重點是在 CPU 上面。

但是可能有人會有疑問,多個線程同時對 total 變量進行自增操作,這么做不會造成沖突嗎?答案是不會的,因為加法是可交換的,即無論是 a + b 還是 b + a,結(jié)果都是相同的。Cython(通過 OpenMP)生成線程代碼,每個線程計算循環(huán)子集的和,然后所有線程再將各自的和匯總在一起。

如果是交給 Numpy 來做的話,那么等價于如下:

np.sum(counts?==?val)?/?float(counts.size)

但是效率如何呢?我們來對比一下:

我們采用并行計算用的是 6.13 毫秒,Numpy 用的是 20 毫秒,看樣子是我們贏了,并且 CPU 核心數(shù)越多,差距越明顯,這便是并行計算的威力。當(dāng)然對于這種算法來說,還是直接交給 Numpy 吧,畢竟人家都幫你封裝好了,一個函數(shù)調(diào)用就可以解決了。

因此有效利用計算機硬件資源確實是最直接的辦法。

并行編程的局限性

雖然 Cython 的 prange 容易使用,但其實還是有局限性的,當(dāng)然這個局限性和 Cython無關(guān),因為理想化的并行擴展本身就是一個難以實現(xiàn)的事情。我們舉個例子:

def filter(nrows, ncols):
????for?i?in?range(nrows):
????????for?j?in?range(ncols):
????????????b[i,?j]?=?(a[i,?j]?+?a[i?-?1,?j]?+?a[i?+?1,?j]?+
???????????????????????a[i,?j?-?1]?+?a[i,?j?+?1])?/?5.0)

假設(shè)我們要做一個過濾器,計算每一個點加上它周圍的四個點的平均值。但如果這里將外層的 range 換成 prange,那么它的整體性能不會明顯提升。因為內(nèi)層循環(huán)訪問的是不連續(xù)的數(shù)組元素,由于缺乏數(shù)據(jù)本地性,CPU 的緩存無法生效,反而導(dǎo)致 prange 變慢。

那么我們什么時候使用 prange 呢?遵循以下法則即可:

  • 1. prange 能夠很好的利用 CPU 并行操作, 這一點我們已經(jīng)說過了;
  • 2. 非本地讀寫的那些和內(nèi)存綁定的操作很難提高速度;
  • 3. 用較少的線程更容易實現(xiàn)加速, 因為對于 CPU 密集而言, 即便指定了超越核心數(shù)的線程也是沒有意義的;
  • 4. 使用優(yōu)化的線程并行庫是將 CPU 所有核心都用于常規(guī)計算的最佳方式;

當(dāng)然,其實我們在開發(fā)的時候是可以隨時使用 prange 的,只要循環(huán)體不和 Python 對象進行交互即可。

小結(jié)

Cython 允許我們繞過全局解釋器鎖,只要我們把和 Python 無關(guān)的代碼分離出來即可。對于那些不需要和 Python 交互的 C 代碼,可以輕松地使用 prange 實現(xiàn)基于線程的并行。

在其它語言中,基于線程的并行很容易出錯,并且難以正確處理。而 Cython 的 prange 則不需要我們在這方面費心,能夠輕松地處理很多性能瓶頸。

到此這篇關(guān)于使用Cython中prange函數(shù)實現(xiàn)for循環(huán)的并行的文章就介紹到這了,更多相關(guān)Cython prange for循環(huán)并行內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python3實現(xiàn)暴力窮舉博客園密碼

    python3實現(xiàn)暴力窮舉博客園密碼

    這篇文章主要介紹了python3實現(xiàn)暴力窮舉博客園密碼的相關(guān)資料,需要的朋友可以參考下
    2016-06-06
  • python將字典內(nèi)容存入mysql實例代碼

    python將字典內(nèi)容存入mysql實例代碼

    這篇文章主要介紹了python將字典內(nèi)容存入mysql實例代碼,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • 詳解Swift中屬性的聲明與作用

    詳解Swift中屬性的聲明與作用

    Swift中的屬性可以被分為存儲屬性和計算屬性,本文將為大家詳解Swift中屬性的聲明與作用,需要的朋友可以參考下
    2016-06-06
  • Python可視化Matplotlib折線圖plot用法詳解

    Python可視化Matplotlib折線圖plot用法詳解

    這篇文章主要為大家介紹了Python可視化中Matplotlib折線圖plot用法的詳解,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進步
    2021-09-09
  • Python實現(xiàn)圖片和視頻的相互轉(zhuǎn)換

    Python實現(xiàn)圖片和視頻的相互轉(zhuǎn)換

    有時候我們需要把很多的圖片合成視頻,或者說自己寫一個腳本去加快或者放慢視頻;也有時候需要把視頻裁剪成圖片,進行后續(xù)操作。這篇文章就將為大家介紹如何通過Python實現(xiàn)圖片和視頻的相互轉(zhuǎn)換,需要的可以參考一下
    2021-12-12
  • Python內(nèi)置類型性能分析過程實例

    Python內(nèi)置類型性能分析過程實例

    這篇文章主要介紹了Python內(nèi)置類型性能分析過程實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Python自定義線程池實現(xiàn)方法分析

    Python自定義線程池實現(xiàn)方法分析

    這篇文章主要介紹了Python自定義線程池實現(xiàn)方法,結(jié)合實例形式較為詳細的分析了Python自定義線程池的概念、原理、實現(xiàn)方法及相關(guān)注意事項,需要的朋友可以參考下
    2018-02-02
  • Python安裝和配置uWSGI的詳細過程

    Python安裝和配置uWSGI的詳細過程

    這篇文章主要介紹了Python uWSGI 安裝配置,本文主要介紹如何部署簡單的 WSGI 應(yīng)用和常見的 Web 框架,以 Ubuntu/Debian 為例給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • Python?Pyinstaller庫安裝步驟以及使用方法

    Python?Pyinstaller庫安裝步驟以及使用方法

    pyinstaller是一個非常簡單的打包python的py文件的庫,下面這篇文章主要給大家介紹了關(guān)于Python?Pyinstaller庫安裝步驟以及使用方法的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • 在IPython中執(zhí)行Python程序文件的示例

    在IPython中執(zhí)行Python程序文件的示例

    今天小編就為大家分享一篇在IPython中執(zhí)行Python程序文件的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-11-11

最新評論