Cython安装

Cython可以通过pip直接进行安装,

pip install cython

Cython代码编译与使用

以一个简短的代码样例来看下Cython如何使用,

定义一个.pyx文件,

# foo.pyx
import math

def pythagorean(a, b):
  return math.sqrt(a**2 + b**2)

使用disutils、cythonize

定义setup.py脚本,

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

setup(
  ext_modules = cythonize('foo.pyx'),
)

在命令行中运行,

python setup.py build_ext --inplace

进入Python命令行,

>>> import foo
>>> foo.pythagorean(3, 4)
5.0

使用pyximport

在开发调试阶段为了简化操作可以使用Cython中定义的pyximport来直接从.pyx文件加载模块,

进入Python命令行,

>>> import pyximport
>>> pyximport.install()
(None, <pyximport.pyximport.PyxImporter object at 0x026F6C30>)
>>> import foo
>>> foo.pythagorean(3, 4)
5.0

性能比较

使用Cython的目的是为了提升性能,因此需要来实际检验一下,扩展foo.pyx增加两个函数,

import math

def pythagorean(a, b):
	return math.sqrt(a**2 + b**2)


def fibonacci(n):
	if n <= 0 or n == 1:
		return 1
	a = 1
	b = 1
	for i in xrange(n - 1):
		c = a + b
		a = b
		b = c
	return c


def factor(n):
	if n <= 1:
		return 1
	return n * factor(n - 1)

这三个函数中pythagorean是简单的小函数,fibonacci存在循环,factor存在递归。纯Python代码可以直接用Cython进行编译,因此先来看一下在代码不变的情况下使用Cython会有怎样的性能变化。

用ipython可以方便的来进行对比,

pip install ipython

在ipython中使用timeit来进行度量,

In [6]: %timeit pythagorean(3, 4)
The slowest run took 14.37 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 290 ns per loop

In [7]: %timeit foo.pythagorean(3, 4)
The slowest run took 15.36 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 292 ns per loop
In [8]: %timeit fibonacci(100)
100000 loops, best of 3: 8.84 µs per loop

In [9]: %timeit foo.fibonacci(100)
100000 loops, best of 3: 5.11 µs per loop
In [12]: %timeit factor(20)
100000 loops, best of 3: 3.6 µs per loop

In [13]: %timeit foo.factor(20)
100000 loops, best of 3: 2.41 µs per loop

上面三组对比可以看到,在Python脚本代码不发生改变情况下,将其用Cython进行编译。如果代码中存在较多的循环或函数调用,那么能够获得一定的性能提升,但如果代码本身很精简,则性能不会有提升,甚至还是Python版本快。

类型信息描述

想要获得更多的性能提升,需要让Cython知道更多信息。静态信息越多,Cython能够获取到的性能提升也就越多,

cpdef int fibonacci(int n):
	cdef int i, a, b, c
	if n <= 0 or n == 1:
		return 1
	a = 1
	b = 1
	for i in xrange(n - 1):
		c = a + b
		a = b
		b = c
	return c

cpdef long long factor(int n):
	if n <= 1:
		return 1
	return n * factor(n - 1)

将foo.pyx中的代码做少许修改,增加函数参数、返回值等类型信息,再来看下性能变化,

In [21]: %timeit fibonacci(100)
100000 loops, best of 3: 7.42 µs per loop

In [22]: %timeit foo.fibonacci(100)
100000 loops, best of 3: 4.62 µs per loop
In [26]: %timeit factor(20)
100000 loops, best of 3: 5.83 µs per loop

In [27]: %timeit foo.factor(20)
100000 loops, best of 3: 2.22 µs per loop

可以看到,在提供了类型信息之后,性能提升幅度增大了不少。在这几个样例中,能进一步提升的余地不多了。但一个利好的结论是Cython能够在对Python代码进行少量修改的情况下,获得幅度客观的性能提升。这种改动甚至不需要对Cython有太深入的了解。

总结

从使用者的需求来说,Python代码面临性能问题寻求优化时,Cython是其中一个选项。从以上代码片段来看,Cython使用上算方便,在不对代码进行大改的情况下可以获得一定的性能提升。但同样的,引入Cython也意味着引入了额外的复杂性。最终是否选择它,还是需要进一步评估其带来的收益,毕竟也可以看到并不是什么代码都能通过它获得高性能增长的。

参考