第5篇:Cython的线性表性操作

線性表是一個線性的集合,它允許用戶在任何位置插入、刪除、訪問和替換元素。

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秒


ss8.png

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 numpycimport 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目錄)的大部分標準庫定義


ss8.png

在我的電腦中,位於~/.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()]
)
ss8.png

更新中.....

你可能感兴趣的:(第5篇:Cython的线性表性操作)