溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何讓Python像Julia一樣快地運(yùn)行

發(fā)布時(shí)間:2021-10-26 17:14:11 來源:億速云 閱讀:94 作者:柒染 欄目:編程語言

如何讓Python像Julia一樣快地運(yùn)行,很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

Julia 與 Python 的比較

我是否應(yīng)丟棄 Python 和其他語言,使用 Julia 執(zhí)行技術(shù)計(jì)算?在看到 http://julialang.org/  上的基準(zhǔn)測試后,人們一定會(huì)這么想。Python和其他高級語言在速度上遠(yuǎn)遠(yuǎn)有些落后。但是,我想到的***個(gè)問題有所不同:Julia 團(tuán)隊(duì)能否以最適合 Python  的方式編寫 Python 基準(zhǔn)測試?

我對這種跨語言比較的觀點(diǎn)是,應(yīng)該根據(jù)要執(zhí)行的任務(wù)來定義基準(zhǔn)測試,然后由語言專家編寫執(zhí)行這些任務(wù)的***代碼。如果代碼全由一個(gè)語言團(tuán)隊(duì)編寫,則存在其他語言未得到***使用的風(fēng)險(xiǎn)。

Julia 團(tuán)隊(duì)有一件事做得對,那就是他們將他們使用的代碼發(fā)布到了 github 上。具體地講,Python 代碼可在此處找到。

***眼看到該代碼,就可以證實(shí)我所害怕的偏見。該代碼是以 C 風(fēng)格編寫的,在數(shù)組和列表上大量使用了循環(huán)。這不是使用 Python 的***方式。

我不會(huì)責(zé)怪 Julia 團(tuán)隊(duì),因?yàn)槲液軆?nèi)疚自己也有同樣的偏見。但我受到了殘酷的教訓(xùn):付出任何代價(jià)都要避免數(shù)組或列表上的循環(huán),因?yàn)樗鼈兇_實(shí)會(huì)拖慢  Python中的速度,請參閱《Python 不是 C》。

考慮到對 C 風(fēng)格的這種偏見,一個(gè)有趣的問題(至少對我而言)是,我們能否改進(jìn)這些基準(zhǔn)測試,更好地使用 Python 及其工具?

在我給出答案之前,我想說我絕不會(huì)試圖貶低 Julia。在進(jìn)一步開發(fā)和改進(jìn)后,Julia 無疑是一種值得關(guān)注的語言。我只是想分析  Python方面的事情。實(shí)際上,我正在以此為借口來探索各種可用于讓代碼更快運(yùn)行的 Python 工具。

在下面的內(nèi)容中,我使用 Docker 鏡像在 Jupyter Notebook 中使用 Python 3.4.3,其中已安裝了所有的 Python  科學(xué)工具組合。我還會(huì)通過Windows 機(jī)器上的 Python 2.7.10,使用 Anaconda 來運(yùn)行代碼。計(jì)時(shí)是對 Python 3.4.3  執(zhí)行的。包含下面的所有基準(zhǔn)測試的完整代碼的 Notebook  可在此處(https://www.ibm.com/developerworks/community/blogs/jfp/resource/julia_python.zip)找到。

鑒于各種社交媒體上的評論,我添加了這樣一句話:我沒有在這里使用 Python 的替代性實(shí)現(xiàn)。我沒有編寫任何  C代碼:如果您不信,可試試尋找分號。本文中使用的所有工具都是 Anaconda 或其他發(fā)行版中提供的標(biāo)準(zhǔn)的 Cython 實(shí)現(xiàn)。下面的所有代碼都在單個(gè)  Notebook中運(yùn)行。

我嘗試過使用來自 github 的 Julia 微性能文件,但不能使用 Julia 0.4.2 原封不動(dòng)地運(yùn)行它。我必須編輯它并將 @timeit  替換為@time,它才能運(yùn)行。在對它們計(jì)時(shí)之前,我還必須添加對計(jì)時(shí)函數(shù)的調(diào)用,否則編譯時(shí)間也將包含在內(nèi)。我使用的文件位于此處。我在用于運(yùn)行 Python  的同一個(gè)機(jī)器上使用 Julia 命令行接口運(yùn)行它。

計(jì)時(shí)代碼

Julia 團(tuán)隊(duì)使用的***項(xiàng)基準(zhǔn)測試是 Fibonacci 函數(shù)的一段簡單編碼。

def fib(n):     if n<2:         return n     return fib(n-1)+fib(n-2)

此函數(shù)的值隨 n 的增加而快速增加,例如:

fib(100) = 354224848179261915075

可以注意到,Python 任意精度 (arbitrary precision) 很方便。在 C 等語言中編寫相同的函數(shù)需要花一些編碼工作來避免整數(shù)溢出。在  Julia中,需要使用 BigInt 類型。

所有 Julia 基準(zhǔn)測試都與運(yùn)行時(shí)間有關(guān)。這是 Julia 中使用和不使用 BigInt 的計(jì)時(shí):

0.000080 seconds (149 allocations:10.167 KB)  0.012717 seconds (262.69 k allocations:4.342 MB)

在 Python Notebook 中獲得運(yùn)行時(shí)間的一種方式是使用神奇的 %timeit。例如,在一個(gè)新單元中鍵入:

%timeit fib(20)

執(zhí)行它會(huì)獲得輸出:

100 loops, best of 3:3.33 ms per loop

這意味著計(jì)時(shí)器執(zhí)行了以下操作:

  1. 運(yùn)行 fib(20) 100 次,存儲總運(yùn)行時(shí)間

  2. 運(yùn)行 fib(20) 100 次,存儲總運(yùn)行時(shí)間

  3. 運(yùn)行 fib(20) 100 次,存儲總運(yùn)行時(shí)間

  4. 從 3 次運(yùn)行中獲取最小的運(yùn)行時(shí)間,將它除以 100,然后輸出結(jié)果,該結(jié)果就是 fib(20) 的***運(yùn)行時(shí)間

這些循環(huán)的大小(100 次和 3 次)會(huì)由計(jì)時(shí)器自動(dòng)調(diào)整??赡軙?huì)根據(jù)被計(jì)時(shí)的代碼的運(yùn)行速度來更改循環(huán)大小。

Python 計(jì)時(shí)與使用了 BigInt 時(shí)的 Julia 計(jì)時(shí)相比出色得多:3 毫秒與 12 毫秒。在使用任意精度時(shí),Python 的速度是 Julia  的 4倍。

但是,Python 比 Julia 默認(rèn)的 64 位整數(shù)要慢。我們看看如何在 Python 中強(qiáng)制使用 64 位整數(shù)。

使用 Cython 編譯

一種編譯方式是使用 Cython 編譯器。這個(gè)編譯器是使用 Python

編寫的。它可以通過以下命令安裝:

pip install Cython

如果使用 Anaconda,安裝會(huì)有所不同。因?yàn)榘惭b有點(diǎn)復(fù)雜,所以我編寫了一篇相關(guān)的博客文章:將 Cython For Anaconda 安裝在  Windows 上

安裝后,我們使用神奇的 %load_ext 將 Cython 加載到 Notebook 中:

%load_ext Cython

然后就可以在我們的 Notebook 中編譯代碼。我們只需要將想要編譯的代碼放在一個(gè)單元中,包括所需的導(dǎo)入語句,使用神奇的 %%cython  啟動(dòng)該單元:

%%cython      def fib_cython(n):      if n<2:          return n      return fib_cython(n-1)+fib_cython(n-2)

執(zhí)行該單元會(huì)無縫地編譯這段代碼。我們?yōu)樵摵瘮?shù)使用一個(gè)稍微不同的名稱,以反映出它是使用  Cython編譯的。當(dāng)然,一般不需要這么做。我們可以將之前的函數(shù)替換為相同名稱的已編譯函數(shù)。

對它計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:1.22 ms per loop

哇,幾乎比最初的 Python 代碼快 3 倍!我們現(xiàn)在比使用 BigInt 的 Julia 快 100 倍。

我們還可以嘗試靜態(tài)類型。使用關(guān)鍵字 cpdef 而不是 def 來聲明該函數(shù)。它使我們能夠使用相應(yīng)的 C 類型來鍵入函數(shù)的參數(shù)。我們的代碼變成了:

%%cython     cpdef long fib_cython_type(long n):     if n<2:       return n     return fib_cython_type(n-1)+fib_cython_type(n-2)

執(zhí)行該單元后,對它計(jì)時(shí)會(huì)得到:

10000 loops, best of 3:36 &micro;s per loop

太棒了,我們現(xiàn)在只花費(fèi)了 36 微秒,比最初的基準(zhǔn)測試快約 100 倍!這與 Julia 所花的 80 毫秒相比更出色。

有人可能會(huì)說,靜態(tài)類型違背了  Python的用途。一般來講,我比較同意這種說法,我們稍后將查看一種在不犧牲性能的情況下避免這種情形的方法。但我并不認(rèn)為這是一個(gè)問題。Fibonacci函數(shù)必須使用整數(shù)來調(diào)用。我們在靜態(tài)類型中失去的是  Python 所提供的任意精度。對于 Fibonacci,使用 C 類型 long

會(huì)限制輸入?yún)?shù)的大小,因?yàn)樘蟮膮?shù)會(huì)導(dǎo)致整數(shù)溢出。

請注意,Julia 計(jì)算也是使用 64 位整數(shù)執(zhí)行的,因此將我們的靜態(tài)類型版本與 Julia 的對比是公平的。

緩存計(jì)算

我們在保留 Python 任意精度的情況下能做得更好。fib 函數(shù)重復(fù)執(zhí)行同一種計(jì)算許多次。例如,fib(20) 將調(diào)用 fib(19)  和fib(18)。fib(19) 將調(diào)用 fib(18) 和 fib(17)。結(jié)果 fib(18) 被調(diào)用了兩次。簡單分析表明,fib(17) 將被調(diào)用 3  次,fib(16) 將被調(diào)用 5 次,等等。

在 Python 3 中,我們可以使用 functools 標(biāo)準(zhǔn)庫來避免這些重復(fù)的計(jì)算。

from functools import lru_cache as cache  @cache(maxsize=None)  def fib_cache(n):      if n<2:          return n      return fib_cache(n-1)+fib_cache(n-2)

對此函數(shù)計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:910 ns per loop

速度又增加了 40 倍,比最初的 Python 代碼快約 3,600 倍!考慮到我們僅向遞歸函數(shù)添加了一條注釋,此結(jié)果非常令人難忘。

Python 2.7 中沒有提供這種自動(dòng)緩存。我們需要顯式地轉(zhuǎn)換代碼,才能避免這種情況下的重復(fù)計(jì)算。

def fib_seq(n):     if n < 2:       return n     a,b = 1,0     for i in range(n-1):       a,b = a+b,a     return a

請注意,此代碼使用了 Python 同時(shí)分配兩個(gè)局部變量的能力。對它計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:1.77 &micro;s per loop

我們又快了 20 倍!讓我們在使用和不使用靜態(tài)類型的情況下編譯我們的函數(shù)。請注意,我們使用了 cdef 關(guān)鍵字來鍵入局部變量。

%%cython     def fib_seq_cython(n):     if n < 2:          return n      a,b = 1,0      for i in range(n-1):          a,b = a+b,a      return a  cpdef long fib_seq_cython_type(long n):      if n < 2:          return n      cdef long a,b      a,b = 1,0      for i in range(n-1):          a,b = a+b,b      return a

我們可在一個(gè)單元中對兩個(gè)版本計(jì)時(shí):

%timeit fib_seq_cython(20)  %timeit fib_seq_cython_type(20)

結(jié)果為:

1000000 loops, best of 3:953 ns per loop  10000000 loops, best of 3:51.9 ns per loop

靜態(tài)類型代碼現(xiàn)在花費(fèi)的時(shí)間為 51.9 納秒,比最初的基準(zhǔn)測試快約 60,000(六萬)倍。

如果我們想計(jì)算任意輸入的 Fibonacci 數(shù),我們應(yīng)堅(jiān)持使用無類型版本,該版本的運(yùn)行速度快 3,500 倍。還不錯(cuò),對吧?

使用 Numba 編譯

讓我們使用另一個(gè)名為 Numba 的工具。它是針對部分 Python 版本的一個(gè)即時(shí)

(jit) 編譯器。它不是對所有 Python 版本都適用,但在適用的情況下,它會(huì)帶來奇跡。

安裝它可能很麻煩。推薦使用像 Anaconda 這樣的 Python 發(fā)行版或一個(gè)已安裝了 Numba 的 Docker 鏡像。完成安裝后,我們導(dǎo)入它的  jit 編譯器:

from numba import jit

它的使用非常簡單。我們僅需要向想要編譯的函數(shù)添加一點(diǎn)修飾。我們的代碼變成了:

@jit  def fib_seq_numba(n):      if n < 2:          return n      (a,b) = (1,0)      for i in range(n-1):          (a,b) = (a+b,a)      return a

對它計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:225 ns per loop

比無類型的 Cython 代碼更快,比最初的 Python 代碼快約 16,000 倍!

使用 Numpy

我們現(xiàn)在來看看第二項(xiàng)基準(zhǔn)測試。它是快速排序算法的實(shí)現(xiàn)。Julia 團(tuán)隊(duì)使用了以下 Python 代碼:

def qsort_kernel(a, lo, hi):      i = lo      j = hi      while i < hi:          pivot = a[(lo+hi) // 2]          while i <= j:              while a[i] < pivot:                  i += 1              while a[j] > pivot:                  j -= 1              if i <= j:                  a[i], a[j] = a[j], a[i]                  i += 1                  j -= 1          if lo < j:              qsort_kernel(a, lo, j)          lo = i          j = hi      return a

我將他們的基準(zhǔn)測試代碼包裝在一個(gè)函數(shù)中:

import random  def benchmark_qsort():      lst = [ random.random() for i in range(1,5000) ]      qsort_kernel(lst, 0, len(lst)-1)

對它計(jì)時(shí)會(huì)得到:

100 loops, best of 3:18.3 ms per loop

上述代碼與 C 代碼非常相似。Cython 應(yīng)該能很好地處理它。除了使用 Cython 和靜態(tài)類型之外,讓我們使用 Numpy

數(shù)組代替列表。在數(shù)組大小較大時(shí),比如數(shù)千個(gè)或更多元素,Numpy 數(shù)組確實(shí)比Python 列表更快。

安裝 Numpy 可能會(huì)花一些時(shí)間,推薦使用 Anaconda 或一個(gè)已安裝了 Python 科學(xué)工具組合的 Docker 鏡像。

在使用 Cython 時(shí),需要將 Numpy 導(dǎo)入到應(yīng)用了 Cython 的單元中。在使用 C 類型時(shí),還必須使用 cimport 將它作為 C  模塊導(dǎo)入。Numpy數(shù)組使用一種表示數(shù)組元素類型和數(shù)組維數(shù)(一維、二維等)的特殊語法來聲明。

%%cython     import numpy as np  cimport numpy as np  cpdef np.ndarray[double, ndim=1] \  qsort_kernel_cython_numpy_type(np.ndarray[double, ndim=1] a, \  long lo, \  long hi):  cdef:  long i, j  double pivot  i = lo  j = hi     while i < hi:       pivot = a[(lo+hi) // 2]       while i <= j:         while a[i] < pivot:           i += 1         while a[j] > pivot:           j -= 1         if i <= j:           a[i], a[j] = a[j], a[i]           i += 1           j -= 1       if lo < j:         qsort_kernel_cython_numpy_type(a, lo, j)       lo = i       j = hi     return a     cpdef benchmark_qsort_numpy_cython():    lst = np.random.rand(5000)    qsort_kernel_cython_numpy_type(lst, 0, len(lst)-1)

對 benchmark_qsort_numpy_cython() 函數(shù)計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:1.32 ms per loop

我們比最初的基準(zhǔn)測試快了約 15 倍,但這仍然不是使用 Python 的***方法。***方法是使用 Numpy 內(nèi)置的  sort()函數(shù)。它的默認(rèn)行為是使用快速排序算法。對此代碼計(jì)時(shí):

def benchmark_sort_numpy():  lst = np.random.rand(5000)  np.sort(lst)

會(huì)得到:

1000 loops, best of 3:350 &micro;s per loop

我們現(xiàn)在比最初的基準(zhǔn)測試快 52 倍!Julia 在該基準(zhǔn)測試上花費(fèi)了 419 微秒,因此編譯的 Python 快 20%。

我知道,一些讀者會(huì)說我不會(huì)進(jìn)行同類比較。我不同意。請記住,我們現(xiàn)在的任務(wù)是使用主機(jī)語言以***的方式排序輸入數(shù)組。在這種情況下,***方法是使用一個(gè)內(nèi)置的函數(shù)。

剖析代碼

我們現(xiàn)在來看看第三個(gè)示例,計(jì)算 Mandelbrodt 集。Julia 團(tuán)隊(duì)使用了這段 Python 代碼:

def mandel(z):      maxiter = 80      c = z      for n in range(maxiter):          if abs(z) > 2:              return n          z = z*z + c       return maxiter   def mandelperf():      r1 = np.linspace(-2.0, 0.5, 26)      r2 = np.linspace(-1.0, 1.0, 21)      return [mandel(complex(r, i)) for r in r1 for i in r2]     assert sum(mandelperf()) == 14791

***一行是一次合理性檢查。對 mandelperf() 函數(shù)計(jì)時(shí)會(huì)得到:

100 loops, best of 3:4.62 ms per loop

使用 Cython 會(huì)得到:

100 loops, best of 3:1.94 ms per loop

還不錯(cuò),但我們可以使用 Numba 做得更好。不幸的是,Numba 還不會(huì)編譯列表推導(dǎo)式 (list

comprehension)。因此,我們不能將它應(yīng)用到第二個(gè)函數(shù),但我們可以將它應(yīng)用到***個(gè)函數(shù)。我們的代碼類似以下代碼。

@jit  def mandel_numba(z):      maxiter = 80      c = z      for n in range(maxiter):          if abs(z) > 2:              return n          z = z*z + c       return maxiter  def mandelperf_numba():      r1 = np.linspace(-2.0, 0.5, 26)      r2 = np.linspace(-1.0, 1.0, 21)      return [mandel_numba(complex(r, i)) for r in r1 for i in r2]

對它計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:503 &micro;s per loop

還不錯(cuò),比 Cython 快 4 倍,比最初的 Python 代碼快 9 倍!

我們還能做得更好嗎?要知道是否能做得更好,一種方式是剖析代碼。內(nèi)置的 %prun 剖析器在這里不夠精確,我們必須使用一個(gè)稱為 line_profiler  的更好的剖析器。它可以通過pip 進(jìn)行安裝:

pip install line_profiler

安裝后,我們需要加載它:

%load_ext line_profiler

然后使用一個(gè)神奇的命令剖析該函數(shù):

%lprun -s -f mandelperf_numba mandelperf_numba()

它在一個(gè)彈出窗口中輸出以下信息。

Timer unit:1e-06 s  Total time:0.003666 s  File:  Function: mandelperf_numba at line 11  Line # Hits Time Per Hit % Time Line Contents  ==============================================================  11 def mandelperf_numba():  12 1 1994 1994.0 54.4 r1 = np.linspace(-2.0, 0.5, 26)  13 1 267 267.0 7.3 r2 = np.linspace(-1.0, 1.0, 21)  14 1 1405 1405.0 38.3 return [mandel_numba(complex(r, i)) for r in r1 for i in r2]

我們看到,大部分時(shí)間都花費(fèi)在了 mandelperf_numba() 函數(shù)的***行和***一行上。***一行有點(diǎn)復(fù)雜,讓我們將它分為兩部分來再次剖析:

def mandelperf_numba():      r1 = np.linspace(-2.0, 0.5, 26)      r2 = np.linspace(-1.0, 1.0, 21)      c3 = [complex(r, i) for r in r1 for i in r2]      return [mandel_numba(c) for c in c3]

剖析器輸出變成:

Timer unit:1e-06 s  Total time:0.002002 s  File:  Function: mandelperf_numba at line 11  Line # Hits Time Per Hit % Time Line Contents  ==============================================================  11 def mandelperf_numba():  12 1 678 678.0 33.9 r1 = np.linspace(-2.0, 0.5, 26)  13 1 235 235.0 11.7 r2 = np.linspace(-1.0, 1.0, 21)  14 1 617 617.0 30.8 c3 = [complex(r, i) for r in r1 for i in r2]  15 1 472 472.0 23.6 return [mandel_numba(c) for c in c3]

我們可以看到,對函數(shù) mandel_numba() 的調(diào)用僅花費(fèi)了總時(shí)間的 1/4。剩余時(shí)間花在 mandelperf_numba()

函數(shù)上?;〞r(shí)間優(yōu)化它是值得的。

再次使用 Numpy

使用 Cython 在這里沒有太大幫助,而且 Numba 不適用。擺脫此困境的一種方法是再次使用 Numpy。我們將以下代碼替換為生成等效結(jié)果的  Numpy

代碼。

return [mandel_numba(complex(r, i)) for r in r1 for i in r2]

此代碼構(gòu)建了所謂的二維網(wǎng)格。它計(jì)算由 r1 和 r2 提供坐標(biāo)的點(diǎn)的復(fù)數(shù)表示。點(diǎn) Pij 的坐標(biāo)為 r1[i] 和 r2[j]。Pij 通過復(fù)數(shù)  r1[i] +

1j*r2[j] 進(jìn)行表示,其中特殊常量 1j 表示單個(gè)虛數(shù) i。

我們可以直接編寫此計(jì)算的代碼:

@jit  def mandelperf_numba_mesh():      width = 26      height = 21      r1 = np.linspace(-2.0, 0.5, width)      r2 = np.linspace(-1.0, 1.0, height)      mandel_set = np.zeros((width,height), dtype=int)      for i in range(width):          for j in range(height):              mandel_set[i,j] = mandel_numba(r1[i] + 1j*r2[j])       return mandel_set

請注意,我將返回值更改為了一個(gè)二維整數(shù)數(shù)組。如果要顯示結(jié)果,該結(jié)果與我們需要的結(jié)果更接近。

對它計(jì)時(shí)會(huì)得到:

10000 loops, best of 3:140 &micro;s per loop

我們比最初的 Python 代碼快約 33 倍!Julia 在該基準(zhǔn)測試上花費(fèi)了 196 微秒,因此編譯的 Python 快 40%。

向量化

讓我們來看另一個(gè)示例。老實(shí)地講,我不確定要度量什么,但這是 Julia 團(tuán)隊(duì)使用的代碼。

def parse_int(t):      for i in range(1,t):          n = random.randint(0,2**32-1)          s = hex(n)          if s[-1]=='L':              s = s[0:-1]          m = int(s,16)          assert m == n      return n

實(shí)際上,Julia 團(tuán)隊(duì)的代碼有一條額外的指令,用于在存在末尾的 &lsquo;L&rsquo; 時(shí)刪除它。我的 Anaconda 安裝需要這一行,但我的 Python  3安裝不需要它,所以我刪除了它。最初的代碼是:

def parse_int(t):      for i in range(1,t):          n = random.randint(0,2**32-1)          s = hex(n)          if s[-1]=='L':              s = s[0:-1]          m = int(s,16)          assert m == n      return n

對修改后的代碼計(jì)時(shí)會(huì)得到:

100 loops, best of 3:3.33 ms per loop

Numba 似乎沒什么幫助。Cython 代碼運(yùn)行速度快了約 5 倍:

1000 loops, best of 3:617 &micro;s per loop

Cython 代碼運(yùn)行速度快了約 5 倍,但這還不足以彌補(bǔ)與 Julia 的差距。

我對此基準(zhǔn)測試感到迷惑不解,我剖析了最初的代碼。以下是結(jié)果:

Timer unit:1e-06 s  Total time:0.013807 s  File:  Function: parse_int at line 1  Line # Hits Time Per Hit % Time Line Contents  ==============================================================  1 def parse_int():  2 1000 699 0.7 5.1 for i in range(1,1000):  3 999 9149 9.2 66.3 n = random.randint(0,2**32-1)  4 999 1024 1.0 7.4 s = hex(n)  5 999 863 0.9 6.3 if s[-1]=='L':  6 s = s[0:-1]  7 999 1334 1.3 9.7 m = int(s,16)  8 999 738 0.7 5.3 assert m == n

可以看到,大部分時(shí)間都花費(fèi)在了生成隨機(jī)數(shù)上。我不確定這是不是該基準(zhǔn)測試的意圖。

加速此測試的一種方式是使用 Numpy 將隨機(jī)數(shù)生成移到循環(huán)之外。我們一次性創(chuàng)建一個(gè)隨機(jī)數(shù)數(shù)組。

def parse_int_vec():      n = np.random.randint(0,2**32-1,1000)          for i in range(1,1000):              ni = n[i]              s = hex(ni)          m = int(s,16)          assert m == ni

對它計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:848 &micro;s per loop

還不錯(cuò),快了 4 倍,接近于 Cython 代碼的速度。

擁有數(shù)組后,通過循環(huán)它來一次向某個(gè)元素應(yīng)用 hex() 和 int() 函數(shù)似乎很傻。好消息是,Numpy  提供了一種向數(shù)組應(yīng)用函數(shù)的方法,而不必使用循環(huán),該函數(shù)是numpy.vectorize()  函數(shù)。此函數(shù)接受一次處理一個(gè)對象的函數(shù)。它返回一個(gè)處理數(shù)組的新函數(shù)。

vhex = np.vectorize(hex)  vint = np.vectorize(int)  def parse_int_numpy():      n = np.random.randint(0,2**32-1,1000)      s = vhex(n)      m = vint(s,16)      np.all(m == n)      return s

此代碼運(yùn)行速度更快了一點(diǎn),幾乎像 Cython 代碼一樣快:

1000 loops, best of 3:703 &micro;s per loop

我肯定 Python 專家能夠比我在這里做得更好,因?yàn)槲也惶煜?Python 解析,但這再一次表明避免 Python 循環(huán)是個(gè)不錯(cuò)的想法。

結(jié)束語

上面介紹了如何加快 Julia 團(tuán)隊(duì)所使用的 4 個(gè)示例的運(yùn)行速度。還有 3 個(gè)例子:

  • pisum 使用 Numba 的運(yùn)行速度快 29 倍。

  • randmatstat 使用 Numpy 可將速度提高 2 倍。

  • randmatmul 很簡單,沒有工具可應(yīng)用到它之上。

包含所有 7 個(gè)示例的完整代碼的 Notebook 可在此處獲得。

我們在一個(gè)表格中總結(jié)一下我們的結(jié)果。我們給出了在最初的 Python 代碼與優(yōu)化的代碼之間實(shí)現(xiàn)的加速。我們還給出了對 Julia  團(tuán)隊(duì)使用的每個(gè)基準(zhǔn)測試示例使用的工具。

如何讓Python像Julia一樣快地運(yùn)行

這個(gè)表格表明,在前 4 個(gè)示例中,優(yōu)化的 Python 代碼比 Julia 更快,后 3 個(gè)示例更慢。請注意,為了公平起見,對于  Fibonacci,我使用了遞歸代碼。

我認(rèn)為這些小型的基準(zhǔn)測試沒有提供哪種語言最快的明確答案。舉例而言, randmatstat 示例處理 5&times;5 矩陣。使用 Numpy  數(shù)組處理它有點(diǎn)小題大做。應(yīng)該使用更大的矩陣執(zhí)行基準(zhǔn)測試。

我相信,應(yīng)該在更復(fù)雜的代碼上對語言執(zhí)行基準(zhǔn)測試。Python 與 Julia 比較&ndash;一個(gè)來自機(jī)器學(xué)習(xí)的示例中提供了一個(gè)不錯(cuò)的示例。在該文章中,Julia  似乎優(yōu)于 Cython。如果我有時(shí)間,我會(huì)使用 Numba試一下。

無論如何,可以說,在這個(gè)小型基準(zhǔn)測試上,使用正確的工具時(shí),Python 的性能與 Julia 的性能不相上下。相反地,我們也可以說,Julia  的性能與編譯后的

Python 不相上下??紤]到 Julia 不需要對代碼進(jìn)行任何注釋或修改,所以這本身就很有趣。

補(bǔ)充說明

我們暫停一會(huì)兒。我們已經(jīng)看到在 Python 代碼性能至關(guān)重要時(shí),應(yīng)該使用許多工具:

  • 使用 line_profiler 執(zhí)行剖析。

  • 編寫更好的 Python 代碼來避免不必要的計(jì)算。

  • 使用向量化的操作和通過 Numpy 來廣播。

  • 使用 Cython 或 Numba 編譯。

使用這些工具來了解它們在哪些地方很有用。與此同時(shí),請謹(jǐn)慎使用這些工具。分析您的代碼,以便可以將精力放在值得優(yōu)化的地方。重寫代碼來讓它變得更快,有時(shí)會(huì)讓它難以理解或通用性降低。因此,僅在得到的加速物有所值時(shí)這么做。Donald  Knuth 曾經(jīng)恰如其分地提出了這條建議:

“我們在 97% 的時(shí)間應(yīng)該忘記較小的效率:不成熟的優(yōu)化是萬惡之源?!?/p>

但是請注意,Knuth 的引語并不意味著優(yōu)化是不值得的,例如,請查看停止錯(cuò)誤地引用 Donald Knuth  的話!和&lsquo;不成熟的優(yōu)化是惡魔&rsquo;的謊言。

Python 代碼可以優(yōu)化,而且應(yīng)該在有意義的時(shí)間和位置進(jìn)行優(yōu)化。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI