線性表是一個線性的集合,它允許用戶在任何位置插入、刪除、訪問和替換元素。
list 與 numpy.ndarray的區別
在Python中的list就是一個簡單的線性表,從細節上看,Python中的列表是由對其他對象的引用組成的連續數組。指向這個數組的指針及其長度被保存在一個列表頭結構中
numpy是Python中科學計算的核心庫。 它提供了高性能的多維數組對象和用於處理這些數組的工具。 NumPy數組是所有相同類型的值的網格,並由非負整數的元組索引。 維數是數組的等級。 數組的形狀是一個整數元組,給出沿每個維度的數組大小。
我們更在乎的是是性能,然而形而上學的結論會誤導他人:
- 大小-Numpy數據結構佔用更少的空間 (這點是沒異議的)
- 性能-numpy.ndarray比list更快 (是嗎?你確認?在你這麼認為理所當然地認為前,請確認的python版本和做個試驗了嗎!!)
本篇將向您展示如何使用Cython加快NumPy數組和list的處理速度。 通過在Python中明確指定變數的數據類型,Cython可以在運行時顯著提高速度。
通過Jupyter Notebook來做如下的測試,Ok,先查看一下Python和Cython的他們版本號
Python的numpy.ndarray
在過去版本的Python中用numpy作為數組類型的計算操作的性能高於list的印象,但從如下圖,僅僅50,000,000的數據量就需要10.65秒,這個結果不禁令人大跌眼鏡
Python的list
這是一個原生的python版本,同樣邏輯的演算法,原生的list測試結果是3.89秒
Cython的list
我們將上面的Python版本的list迴圈修改相應的Cython版本,測試結果為3.32秒
Cython的numpy
我們將上面的Python版本的numpy.ndarray迴圈修改相應的Cython版本,測試結果為3.82秒
那麼測試結果就如下圖所示:
就目前的測試效果看來Cython版本的list運行速度是最優的方案
Python 3.6.9這一版似乎在for in迴圈結構以及list的性能優化下相比以往版本下了很大苦功,這一點值得點贊的。
現在進行進入主題,我們分析一下上面numpy優化的一些細節,先前我們看到,在為所使用的變數明確定義C類型之後,Cython代碼可以非常快速地運行。 NumPy數組也是如此。 如果我們將NumPy數組保留為當前形式,其工作方式仍然按照Python工作方式完全相同,它會為數組中的每個數字創建一個對象。 為了numpy數組運行更快,我們還需要為numPy數組定義C數據類型,就像其他任何變數一樣。
numpy數組的數據類型是ndarray,代表n維數組。 如果使用關鍵字int創建整數類型的變數,則可以使用ndarray為numpy數組創建變數。 注意,必須使用numpy調用ndarray,因為ndarray在numpy內部。 因此,用於創建numpy數組變數的語法為numpy.ndarray。 下麵列出的代碼創建一個名為arr的變數,其數據類型為numpy.ndarray。
首先要注意的是,NumPy是在第二行中使用常規關鍵字import導入的。 在第三行中,您可能會注意到NumPy也使用關鍵字cimport導入。
現在可以看到Cython檔可以分為兩類:
- 定義檔(.pxd)
- 實現檔(.pyx)
定義檔的擴展名為.pxd,用於保存C聲明,例如要導入並在其他Cython檔中使用的數據類型。 另一個檔是擴展名為.pyx的實現檔,我們目前正在使用該檔編寫Cython代碼。 在此檔中,我們可以導入定義檔以使用其中聲明的內!
容。
下麵的代碼將寫入擴展名為.pyx的實現檔中。 cimport numpy語句在Cython中導入名為"numpy"的定義檔。 這樣做是因為Cython的"numpy"檔具有用於處理NumPy數組的數據類型。
cimport語句
第3-4行使用了兩個import語句,import numpy和cimport numpy
這與哪一個相關? 在這裏,我們將使用cimport numpy,而不是普通的import語句。 因為,由於我們在第11行聲明了arr的變數類型是numpy.ndarray的類型,即cdef numpy.ndarray arr,對於Cython編譯器來說,它需要導入定義該numpy.ndarray類型的pyd檔,因此就需要cimport numpy
我們驗證一下,在Python命令提示符下使用 help("Cython")函數查看Cython擴展所在的目錄,如下圖底部,我們找到對應的目錄
我們在Cython擴展的所在目錄會找到一個叫Includes的子目錄,該目錄不僅包含numpy的pxd檔,還這包含Cython集成C庫(即libc目錄)和C++庫(即libcpp目錄)的大部分標準庫定義
在我的電腦中,位於~/.local/lib/python3.6/site-packages/Cython/Includes/numpy這個目錄下就有關於numpy.ndarray的類定義,在Cython編程中,cimport語句就相當與C/C++編程中的include語句
Cython中numpy數組類型
除了定義變數arr數據類型之外,我們還可以定義另外兩條資訊:
- ndarray元素的數據類型:數組元素的數據類型為int,並根據下麵的行定義。 使用cimport導入的numpy具有numpy中的每種類型相對應的類型,但末尾帶有_t。 例如,常規numpy中的int對應於Cython中的int_t。
- 維度數:參數是ndim,它指定數組中的維數。 此處設置為1。 請注意,其默認值也是1,因此可以從我們的示例中省略。 如果使用更多尺寸,則必須指定它。
那麼第11行的arr變數聲明
cdef numpy.ndarray arr
可以定義得更為具體聲明語句
cdef numpy.ndarray[numpy.int_t,ndim=1] arr
Cython的numpy.ndarray類型聲明和函數
很遺憾的是,僅當numpy數組是函數內部的參數或函數中的局部變數(而不是腳本主體,即全局作用域)時,才可以這種方式定義numpy數組的類型。 現在,我們需要編輯先前的代碼,以將其添加到將在下一部分中創建的函數中。 現在,讓我們在定義數組之後創建它。
請注意,我們將變數arr的類型定義為numpy.ndarray,但請不要忘記這是容器的類型。 此容器具有元素,如果未指定其他元素,則將這些元素轉換為對象。 為了將這些元素強制為整數,我會將dtype參數設置為numpy.int。此處使用的numpy是使用cimport關鍵字導入的numpy。 通常,每當找到用於定義變數的關鍵字numpy時,請確保使用cimport關鍵字將其從Cython導入。
cdef numpy.ndarray[numpy.int_t,ndim=1] arr
arr=numpy.arange(maxval,dtype=numpy.int)
以下是使用cdef 函數封裝後的array_sum函數,參數的numpy.ndarray類型即 numpy.ndarray[DTYPE_t,ndim=1],如果有C基礎的同學,應該不難看出這不就是等同於C中指向數組的指針
import time
import numpy
cimport numpy
ctypedef numpy.int_t DTYPE_t
cdef array_sum(numpy.ndarray[DTYPE_t,ndim=1] arr):
cdef int n
cdef unsigned long long total
cdef int k
cdef double t1,t2
t1=time.time()
for k in arr:
total=total+k
print("total=",total)
t2=time.time()
print("耗時:{}".format(t2-t1))
#end-def
OK,我們嘗試編譯上面Cython程式,我建議使用setup.py的方式去編譯,因為我們需要將numpy的Python代碼編譯成C代碼還需要在setup.py檔中顯式設定include_dirs參數,該參數包含numpy相關的C/C++頭檔,
from distutils.core import setup
from Cython.Build import cythonize
import numpy
setup(
name="array_sum",
ext_modules=cythonize("./array.pyx"),
include_dirs=[numpy.get_include()]
)
更新中.....