賈維斯的智慧工坊

  • Home
  • About
  • Note
  • Project
  • Experience
  • Service
  • Sitemap


MY | NOTES

  1. 首頁
  2. >
  3. 筆記
  4. >
  5. 分享

[Python] 程式加速:Cython環境安裝與範例 (Win10)

Cython environment setup and examples in Windows
Jul, 2019

讓Python的執行速度變快,有以下幾種作法:

  • PyInstaller:提前編譯成exe執行檔
  • Numba:即時編譯(jit)提升Numpy速度
  • Cython:將程式轉成c的Binary

    此篇要介紹Cython
    什麼都不用改,就能節省至少50%時間!
    更多精彩文章
    Python技巧:新手提升效率的必學語法
    [Python] 速度比較:Numpy與內建函式
    [心得] 無瑕的程式碼:敏捷軟體開發技巧
    如何改寫程式?MATLAB轉Python總整理
    [TensorFlow] 環境安裝(Anaconda與GPU加速)

    1、環境建設

    套件安裝

    使用Anaconda
    conda install -c anaconda cython
    
    或使用pip
    pip install cython
    

    C/C++編譯環境

    如果是Linux就內建GCC編譯環境
    但Windows就必須自行安裝Visual Studio
    否則會出現 windows install cython error
    "unable to find vcvarsall.bat"

    安裝版本對應如下
    Visual C++ 9.0 (VS 2008) -> CPython 2.6, 2.7, 3.0, 3.1, 3.2
    Visual C++ 10.0 (VS 2010) -> CPython 3.3, 3.4
    Visual C++ 14.0 (VS 2015up) -> CPython 3.5, 3.6
    若不想安裝整包Visual Studio,可以只安裝Visual C++

    1、到官網,選擇 Visual Studio 2019 的工具
    2、下載 Build Tools for Visual Studio 2019
    3、安裝左上角第一個 [C++建置工具] 即可,約5G多

    2、程式範例

    首先準備三個檔案

    method_old.py

    欲轉換的原始程式碼
    def test(x):
        y = 0
        for i in range(x):
            y = y + i
        return y
    
    def prime(givenNumber):
        primes = []
        for possiblePrime in range(2, givenNumber + 1):
            isPrime = 1
            for num in range(2, possiblePrime):
                if possiblePrime % num == 0:
                    isPrime = 0
            if isPrime:
                primes.append(possiblePrime)
        return primes
    

    method_new.pyx

    將原始程式碼的副檔名改成pyx
    什麼都不用改就能做後面的轉換
    但想要加速更快,可將變數給予cdef+型態
    有回傳值的函式則為cpdef

    常見的型態如:
    cdef int x
    cdef int[10] x
    cdef float x = 0
    cdef double x = 0
    cdef list x
    cdef dict x
    cdef object x

    在下範例中,找質數prime1()就是沒改變任何東西
    prime2則為有加上cdef
    兩種都能進行轉換
    cpdef double test(int x):
        cdef double y = 0
        cdef double i
        for i in range(x):
            y = y + i
        return y
    
    # 與method_old.py一模一樣
    def prime1(givenNumber):
        primes = []
        for possiblePrime in range(2, givenNumber + 1):
            isPrime = 1
            for num in range(2, possiblePrime):
                if possiblePrime % num == 0:
                    isPrime = 0
            if isPrime:
                primes.append(possiblePrime)
        return primes
    
    # 有加入宣告
    cpdef list prime2(int givenNumber):
        cdef list primes = []
        cdef unsigned int possiblePrime
        cdef unsigned int num
        cdef int isPrime
        for possiblePrime in range(2, givenNumber + 1):
            isPrime = 1
            for num in range(2, possiblePrime):
                if possiblePrime % num == 0:
                    isPrime = 0
            if isPrime:
                primes.append(possiblePrime)
        return primes
    

    setup.py

    告訴Cython要轉換哪個檔案,如method_new.pyx
    from distutils.core import setup
    from Cython.Build import cythonize
    
    setup(
       ext_modules = cythonize("method_new.pyx")
    )
    

    執行轉換

    開啟命令提示字元(or Anaconda Prompt),到該路徑輸入指令
    python setup.py build_ext --inplace
    >> 正在產生程式碼
    >> 已完成程式碼產生
    
    會產生 .c 和 .pyd 檔,後者是主要用來執行的binary檔
    如果沒在專案中路徑看到 .pyd 檔,需自行到 build 資料夾中
    將 .pyd 複製出來專案路徑,與 main.py 放在一起
    這樣才能繼續下一步 import 測試

    3、測試結果

    再新增一個測試檔案

    main.py

    old就是原始程式、new則是經過Cython編譯後
    import method_old as old
    import method_new as new
    import time
    
    print(old.test(50000000))
    print(new.test(50000000))
    
    start = time.time()
    old.prime(50000)
    print('Old:', '%.2f'%(time.time() - start), 'sec')
    
    start = time.time()
    new.prime1(50000)
    print('Cython1:', '%.2f'%(time.time() - start), 'sec')
    
    start = time.time()
    new.prime2(50000)
    print('Cython2:', '%.2f'%(time.time() - start), 'sec')
    
    執行結果與時間:
    1249999975000000
    1249999975000000.0
    Old: 82.44 sec
    Cython1: 46.82 sec
    Cython2: 2.98 sec
    
    test()累加0~50000000都是一樣結果
    找50000以內的質數,原始的要82秒
    什麼都沒改,直接轉換的prime1(),46秒
    如果有用cdef宣告再轉換的prime2(),直接變3秒...
    這結果太驚人了!!!

    不過Cython並非能加速每種情況
    在定義cdef時,也要用C/C++的思維
    如變數一定要在最外層宣告
    陣列尺寸都要符合運算等等
    歡迎大家在底下留言:你執行後出現的秒數為何?

    4、何時該加cdef宣告?

    在命令提示字元輸入
    cython -a method_new.pyx
    
    會產生method_new.html
    用Chrome開啟後可以看到

    黃色越深,代表越建議加上cdef的宣告
    幫助可以提升速度
    左邊有個+號,點開就能看到Cython轉換後的C語言




  • Reference

    [1] Optimizing with Cython Introduction - Cython Tutorial
    [2] Compiling Python Code with Cython


    ← Back to note