利用 Cython 加速 Python 代码

先从一个例子来说,一个斐波那契数列数列例子说起:

1
2
3
4
5
6
7
8
9
10
11
12
13
# fib.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-


def fib(i):
a = 0
b = 1
while i > 0:
i -= 1
a, b = b, a+b

return True
1
2
3
4
5
6
7
8
9
10
11
12
# try_c.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from fib import fib

startTime = time.time()
for i in xrange(1,10):
fib(10**6)

print time.time() - startTime

在 CPython 下执行效果如下:

➜  cython  python try_c.py
429.003911972

换成 pypy 的话,效率会更高一些:

(pypy)➜  cython  pypy try_c.py
355.186796904

能不能有更快的效率呢?Python 的执行效率确实令人诟病,如果有 Python 的编写速度,C 的执行效率更好了(你说的是 Golang 嘛?)。这就是需要 Cython 出场了。

执行下面的命令就可以安装 Cython 了:

pip install cython

然后编写一个 setup.py 文件:

1
2
3
4
5
6
7
8
9
10
11

# setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

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

setup(name ='cython app',
ext_modules = cythonize("fib.pyx"),
)

将 fib.py 文件重命名为 fib.pyx 文件,然后执行下面命令就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
(pypy)➜  cython  pypy setup.py build_ext --inplace
running build_ext
building 'fib' extension
cc -arch x86_64 -O2 -fPIC -Wimplicit -I/Developer/pypy/include -c fib.c -o build/temp.macosx-10.9-x86_64-2.7/fib.o
fib.c:58:9: warning: 'Py_OptimizeFlag' macro redefined
#define Py_OptimizeFlag 0
^
/Developer/pypy/include/pypy_macros.h:613:9: note: previous
definition is here
#define Py_OptimizeFlag PyPy_OptimizeFlag
^
1 warning generated.
cc -shared -undefined dynamic_lookup -arch x86_64 build/temp.macosx-10.9-x86_64-2.7/fib.o -o /Desktop/cython/fib.pypy-23.so

具体执行的代码如下:

1
2
3
cython fib.pyx
cc -arch x86_64 -O2 -fPIC -Wimplicit -I/Users/jetlee/Developer/pypy/include -c fib.c -o build/temp.macosx-10.9-x86_64-2.7/fib.o
cc -shared -undefined dynamic_lookup -arch x86_64 build/temp.macosx-10.9-x86_64-2.7/fib.o -o /Users/jetlee/Desktop/cython/fib.pypy-23.so

这里有个很奇怪的地方,cython 生成的是 so 文件,我的系统是 mac,shared library 是. dylib 文件,不过 cython 这里只认识 so 文件,所以自己不要自以为是的改名了…

1
2
(pypy)➜  cython  pypy try_c.py
208.884904146

再测试一下 CPython 的结果:

1
2
(pypy)➜  cython  python try_c.py
215.461463928

上面的结果不是同样条件测试的,会对结果有一定的影响。具体情况下的结果还请自行测试。

除了利用 Cython 外,还有其他几种方法用来加速,这个可以自行搜索一下,比如 cffi,具体不在这里赘述。我个人比较倾向于 Cython 的方法,这种方法可以实现无需修改代码的加速(如上),同样也可以实现更高效的代码级别编译,效率更高。但是缺点是绕不开 python 的 GIL 问题,具体的可以看一下 这篇文章 介绍 GIL 问题。