Cython - 運算與溢位

今天在使用 Cython 時踩到一個小雷,主要是因為 Python 是為了科學計算而生所以能處理很大的數字。 使用 Cython 時若將變數轉成 C 的型別可能會有溢位的情形。

1. Source code:

建立一個 frac.pyx 加入下面兩個 functions,一個是 C 版本另一個是 Python 版本。

cimport cython

@cython.infer_types(True)
cpdef cp_fract(int n):
    res = 1
    for i in range(1, n+1):
        res *= i
    return res

def py_fract(n):
    res = 1
    for i in range(1, n+1):
        res *= i
    return res

建立一個 setup.py 用來 build Cython。

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

setup(ext_modules=cythonize("frac.pyx"))

寫一個簡單的 makefile 來記編譯的指令,make 後就能在一般 Python 檔 import frac 模組。

build: frac.pyx
	python3 setup.py build_ext --inplace

2. 時間測試:

簡單在不溢位的情形下測試,f 是乘積的次數 n 是計算的次數。

def test_speed(f=20, n=100000):
    print(f"Python value: {py_frac(f)}")
    start = time()
    for _ in range(n):
        py_frac(f)
    end = time()
    print(f"python takes: {end-start} s")

    print(f"C value: {cp_frac(f)}")
    start = time()
    for _ in range(n):
        cp_frac(f)
    end = time()
    print(f"C takes: {end-start} s")

執行結果如下。可以發現 Python 跟 C 計算的值是一樣的但時間大約差 10 倍。

Python value: 2432902008176640000
python takes: 0.08506226539611816 s
C value: 2432902008176640000
C takes: 0.008076906204223633 s

3. 溢位測試:

我們把乘積的次數增加進行溢位測試。

def test_overflow(f=40):
    print(f"Python value: {py_frac(f)}")
    print(f"C value: {cp_frac(f)}")

執行結果如下。可以發現 Python 的 object 可以儲存大的數字但 C 會有溢位的情形。

Python value: 815915283247897734345611269596115894272000000000
C value: -70609262346240000

4. 小結論:

使用 cython.infer_types 時要小心將 Python object 轉換成 C 時的溢位情形。


留言

熱門文章