Cython的用法以及填坑姿式

由於項目須要,須要優化已有的Python代碼。目前Python代碼的執行過程是將Python代碼轉變成一行行指令,而後解釋器解釋指令的執行,調用到C代碼層。若是去掉指令解釋這個階段,直接進入C代碼層,效率就比較高了。若是用以前所述的使用Python C API將Python代碼改造爲C代碼並做爲Python的內建模塊,工做量極其大,也不能保證其正確性,因此這種方法不太現實。而Cython庫正好符合這種場景需求,將已有的Python代碼轉化爲C語言的代碼,並做爲Python的built-in模塊擴展。python

版本說明:linux

Python 2.7.13  (CPython)git

Cython 0.25.2github

Python的文件類型介紹:編程

.py       python的源代碼文件編程語言

.pyc     Python源代碼import後,編譯生成的字節碼編輯器

.pyo     Python源代碼編譯優化生成的字節碼。pyo比pyc並無優化多少,只是去掉了斷言函數

.pyd     Python的動態連接庫(Windows平臺)測試

.py, .pyc, .pyo 運行速度幾乎無差異,只是pyc, pyo文件加載的速度更快,不能用文本編輯器查看內容,反編譯不太容易優化

 

本文的目標是將test.py文件生成test.c文件,而後將test.c文件做爲Python源碼的一部分,從新編譯生成Python,使用時直接import test便可使用test模塊。

Cython基本介紹:

文檔中這樣總結Cython:

Cython is an optimising static compiler for both the Python programming language and the extended Cython programming language (based on Pyrex). It makes writing C extensions for Python as easy as Python itself.

是一個Python編程語言的編譯器,寫C擴展就像寫Python代碼同樣容易。

其最重要的功能是:

  • write Python code that calls back and forth from and to C or C++ code natively at any point.

即 將Python代碼翻譯爲C代碼。以後就能夠像前面文章介紹的C語言擴展Python模塊使用這些C代碼了。

 

Cython基本用法:

 在使用Cython編譯Python代碼時,務必要安裝C/C++編譯器,本文是直接安裝了Visiual Studio 2015的開發環境。

1. 安裝Cython庫

   pip install Cython

 就是如此簡單明瞭

2. 編寫一個測試代碼文件test.py放在D:/test/test.py

def say_hello():
    print "hello world"

而後在同一目錄下,新建一個setup.py文件,內容以下:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize("test.py"))

cythonize()是Cython提供將Python代碼轉換成C代碼的API,

setup是Python提供的一種發佈Python模塊的方法。

3. 使用命令行編譯Python代碼:

python setup.py build_ext  --inplace

若是出現這種狀況是由於沒有C編譯器相關的配置沒有設置好,在Windows上通常採用Microsoft VisualStudio,不一樣的VS版本設置不一樣。

  • Visual Studio 2010 (VS10): SET VS90COMNTOOLS=%VS100COMNTOOLS%
  • Visual Studio 2012 (VS11): SET VS90COMNTOOLS=%VS110COMNTOOLS%
  • Visual Studio 2013 (VS12): SET VS90COMNTOOLS=%VS120COMNTOOLS%
  • Visual Studio 2015 (VS14): SET VS90COMNTOOLS=%VS140COMNTOOLS%
  • Visual Studio 2017 (VS14): SET VS90COMNTOOLS=%VS150COMNTOOLS% 

這裏採用VS2015做爲C的編譯器。

在命令行中輸入SET VS90COMNTOOLS=%VS140COMNTOOLS%

而後輸入編譯命令:python setup.py build_ext --inplace

最終的生成結果以下:

在D:/test/ 目錄中:

test.c是test.py轉化後的C代碼文件,能夠看到test.c很是大!!

test.pyd是python的動態連接庫,咱們在使用import test時會加載

build目錄編譯過程當中生成的臨時文件

使用剛剛生成的test模塊,就像使用Python的任意模塊同樣:

 

這裏稍微解釋一下 命令行:python setup.py build_ext --inplace

build_ext是指明python生成C/C++的擴展模塊(build C/C++ extensions (compile/link to build directory))

--inplace指示 將編譯後的擴展模塊直接放在與test.py同級的目錄中。

 

整個Cython工做的流程以下圖所示:

分兩步:

1).py文件使用Cython被編譯爲.c文件;

2).c文件使用C編譯器生成.pyd(windos)或.so(linux)文件。

 除了這種廣泛的用法外,還能夠在Python代碼的某些地方加上靜態類型聲明,也能夠更進一步提高Python的運行效率,這些屬於小技巧了~

好比:

def say_hello(int s):
    cdef int a = 2
    print s + 2

s和a變量直接指示爲int類型,不用再作動態語言的類型推斷了。

 

小測試:

import math
import time

def f():
    time1 = time.time()
    for i in range(100000000):
        x = math.sqrt(i)
    time2 = time.time()
    print time2 - time1

這段原生的Python代碼運行時間是13.17秒,使用Cython優化後,運行時間爲9.36秒。基本上提高30%。其實Cython通常對外聲稱的效率提高也大概是這麼多。

 

Cython中的坑

在這一小節中,討論Cython中的一些坑以及填坑姿式。Cython官方文檔中已經明確指出一些不支持的Python特性,有些不打算修復,再結合具體項目場景,給出一些坑的解決方案。

具體項目需求: 將一些須要優化的Python代碼模塊翻譯成C代碼,加入項目中,編譯連接以後,做爲Python的一個built-in模塊。

因此,只須要轉換成C代碼這一步驟便可,不須要使用Python提供的distutils模塊,只須要Cython提供的cythonize。

1. 從Python的site-package中提取install的Cython目錄,獨立出來。由於是供給其餘人使用,其餘人pip install cython的話可能版本不一致,會出現一些問題。

Cython目錄是Cython源碼以及Python2.7/Lib/site-package下的cython.py,即:

CythonTool是封裝了轉化爲C代碼的py腳本文件。

在使用時,須要設置一下sys.path,在import時才能找到咱們獨立出來的Cython模塊。

# import Cython path
sys.path.insert(0, cython_path) from Cython.Build import cythonize from Cython.Compiler import Options

在sys.path的頭部添加cython_path,因此Python site-package裏的Cython就不會影響咱們獨立出來的Cython模塊。

2. 在編譯python代碼爲C代碼時,須要指定輸出的C代碼文件路徑,Cython默認的是python腳本目錄,這樣會致使py文件與.c文件混在一塊兒,很容易就亂了。

目前工做目錄有三個

LibDir:  須要優化的Python腳本所在目錄

CfileDir: 輸出的C代碼文件所在的目錄

ToolDir:  封裝的cython優化腳本所在的目錄,其做用是將LibDir中的Python模塊轉換爲C代碼,而後輸出到CfileDir

故而封裝的cython腳本工做目錄在ToolDir,腳本中最核心的是代碼是:

cythonize(pyfilePath, build_dir=CfileDir)

使用build_dir參數指明C代碼輸出目錄。

看起來很完美,可是Cython源碼在這裏裏有個坑。

當指定build_dir時,當pyfilePath與CfileDir都爲絕對路徑時,且cython腳本的工做目錄與pyfilePath不一致時,cythonize會將輸出文件的目錄置爲pyfilePath所在的目錄,故最後輸出的C代碼文件不會到CfileDir裏。

因此應該在封裝的cython腳本里調用os.chdir(LibDir),轉換完成時再切換到原有工做目錄。牢記cython的工做目錄應該與待優化的python腳本目錄一致。

緣由:cythonize中的實現有這樣一段代碼:【調試狀態下】

 

紅色框中,若是c_file是一個絕對文件名時,會出現如下狀況,至於c_file爲何會是一個絕對的文件名,是由於cython的工做目錄與待優化腳本目錄不匹配致使的。

 

 3. 原始的Cython對Python的Package支持度不夠,一個大坑!!

只能經過修改Cython的源碼來填坑。

原始的Cython編譯Python以後,生成的C代碼裏有兩個關鍵的地方,拿test模塊爲例:

這裏定義了test模塊初始化函數,這個函數裏會有建立test模塊的代碼部分:

當import時,Python解釋器會調用這裏,初始化test模塊,將test名字加入到sys.builtin_module_names中。

測試發現,若是有D:/Lib/mypackate/test.py , 編譯後,生成的C代碼與D:/Lib/test.py生成的代碼並沒有不一樣,即mypackate這個包被忽略了,致使生成的C代碼沒有了包依賴關係。

順着代碼閱讀,最終肯定了問題出現的源頭,Cython/Compiler/ModuleNode.py, 修改了此文件中的兩個函數:

1)生成模塊init代碼函數:full_module_name替換掉env.module_name, 即用initmypackage_test替換init_test

2) 修改了建立模塊時傳入的模塊名規則,並考慮到mypackage/__init__.py這種狀況, 對於package來講須要加入__path__用以標識這個對象不是普通的Python模塊,而是一個包。

 

 4.  深坑。 inspect、types相關。

Inspect模塊中有各類類型判斷函數,好比 isfunction, ismethod, ismodule等。這裏的坑是:

cython化的函數類型變爲了cython_function_or_method,而原始python的函數類型是function,因此若是待優化的Python腳本中使用isfunction(func, types.FunctionType)時,若是func是原始的函數則返回True,而cython化的函數返回False. 除了function類型外還有generator, functionType.func_globals類型也存在不一致。

目前在inspect.py的isfunction中加入了trick,會判斷

type(func).__name__=="cython_function_or_method". 而且types.py模塊不被cython化,那麼若是調用inspect.isfunction(func, types.FunctionType)對於原始的Python函數仍是cython化的函數都沒有問題了。

可是若是直接使用isinstance(func, types.FunctionType)仍然會存在問題,types.FunctionType只對原始的python函數判斷正確。

比較繞,總而言之一句話,python裏的類型和cython化後的對應的類型可能會不一樣。我總結了大部分python類型,其中有幾個cython化後類型不一致:

沒有什麼太好的解決辦法,要麼改寫inspect模塊,但還要保證Python代碼不能直接使用types模塊,要麼修改Python源碼中關於isinstance的實現。

5. 官方文檔中列出的坑

1) 不支持Nested tuple, Python2中的特性,Python3不支持了。因此Cython直接不支持Nested tuple特性

2)找不到變量名:You can disable the latter behaviour by setting "error_on_unknown_names" to

 解決辦法:

3)Stack Frames. 

 Cython不支持Stack Frame。

 

總結:能夠考慮使用Cython優化一些簡單的Python項目,若是用到很是複雜的場景的話,有些語法的特性不支持,會有繞不過去的坑

 

參考資料:

https://github.com/cython/cython

https://mdqinc.com/blog/2011/08/statically-linking-python-with-cython-generated-modules-and-packages/

相關文章
相關標籤/搜索