将Cython添加到SciPy¶
就像写在 Cython website :
Cython是一个针对Python编程语言和扩展Cython编程语言(基于Pyrex)的优化静电编译器。它使编写Python的C扩展与编写Python本身一样简单。
如果您的代码目前在Python中执行了大量循环,那么使用Cython编译可能会让它受益。本文档旨在进行非常简短的介绍:仅足以了解如何将Cython与SciPy一起使用。编译代码后,您可以通过查看 Cython documentation 。
为了让SciPy使用Cython编译您的代码,您只需要做两件事:
将您的代码包含在包含
.pyx
扩展名,而不是.py
分机。所有带有.pyx
扩展名由Cython自动转换为.c
在构建SciPy时创建文件。从此添加扩展名
.c
文件添加到代码所在的子包的配置中。通常,这非常简单:在子包的setup.py
文件。添加为扩展后,.c
构建SciPy时,代码将由您的C编译器编译为机器码。
示例¶
scipy.optimize._linprog_rs.py
包含修改后的单纯形方法的实现。 scipy.optimize.linprog
。改进后的单纯形法对矩阵进行了大量的初等行运算,是一种很自然的元素化候选方法。
请注意, scipy/optimize/_linprog_rs.py
导入 BGLU
和 LU
来自以下位置的类 ._bglu_dense
就像它们是普通的Python类一样。但他们不是。 BGLU
和 LU
中是否定义了Cython类 /scipy/optimize/_bglu_dense.pyx
. 关于它们的导入或使用方式,没有任何迹象表明它们是用Cython编写的;到目前为止,我们可以确定它们是Cython类的唯一方法是,它们是在一个文件中用 .pyx
分机。
即使是在 /scipy/optimize/_bglu_dense.pyx
,大部分代码类似于Python。最显著的不同之处在于 cimport
, cdef
, 和 Cython decorators 。这些都不是严格必要的。没有它们,纯Python代码仍然可以由Cython编译。Cython语言扩展是*just* 调整以提高性能。这 .pyx
文件自动转换为 .c
在构建SciPy时由Cython创建文件。
剩下的唯一事情就是从这里添加一个扩展 .c
文件使用 numpy.distutils
. 这只需要一行代码 scipy/optimize/setup.py
: config.add_extension('_bglu_dense', sources=['_bglu_dense.c'])
, _bglu_dense.c
是来源,并且 _bglu_dense
是扩展名(用于一致性)。当SciPy建成后, _bglu_dense.c
将被编译为机器码,并且我们将能够导入 LU
和 BGLU
扩展中的类 _bglu_dense
。
锻炼身体¶
See a video run-through of this exercise: Cythonizing SciPy Code
更新Cython并创建新分支(例如,
git checkout -b cython_test
),其中对SciPy进行了一些试验性更改将一些简单的Python代码添加到
.py
文件中的/scipy/optimize
目录,比方说/scipy/optimize/mypython.py
。例如:def myfun(): i = 1 while i < 10000000: i += 1 return i
让我们看看这个纯Python循环需要多长时间,以便我们可以比较Cython的性能。例如,在Spyder的IPython控制台中:
from scipy.optimize.mypython import myfun %timeit myfun()
我得到的信息是这样的:
715 ms ± 10.7 ms per loop
省下你的
.py
文件添加到.pyx
文件,例如mycython.pyx
。建立SciPy。请注意,一个
.c
文件已添加到/scipy/optimize
目录。在类似行附近的某个位置,从您的
.c
文件到/scipy/optimize/setup.py
。例如:config.add_extension('_group_columns', sources=['_group_columns.c'],) # was already here config.add_extension('mycython', sources=['mycython.c'],) # this was new config.add_extension('_bglu_dense', sources=['_bglu_dense.c']) # was already there
重建本网站。请注意,一个
.so
文件已添加到/scipy/optimize
目录。计时:
from scipy.optimize.mycython import myfun %timeit myfun()
我得到的信息是这样的:
359 ms ± 6.98 ms per loop
Cython将纯Python代码的速度提高了约2倍。
这对事情的计划来说并没有多大的改善。要了解原因,让Cython创建代码的“带注释”版本以显示瓶颈是很有帮助的。在终端窗口中,在
.pyx
文件与-a
标志:cython -a scipy/optimize/mycython.pyx
请注意,这将创建一个新的
.html
文件中的/scipy/optimize
目录。打开.html
任何浏览器中的文件。文件中以黄色突出显示的行表示编译后的代码与Python之间的潜在交互,这会大大降低速度。突出显示的强度表示估计的交互严重程度。在这种情况下,如果我们定义变量,很多交互都可以避免
i
作为整数,这样Cython就不必考虑它是否可能是通用Python对象:def myfun(): cdef int i = 1 # our first line of Cython code while i < 10000000: i += 1 return i
重新创建带注释的
.html
文件显示,大部分Python交互已经消失。重新构建SciPy,打开全新的IPython控制台,然后
%timeit
:
from scipy.optimize.mycython import myfun
%timeit myfun()
我得到的信息是这样的: 68.6 ns ± 1.95 ns per loop
。Cython代码的运行速度大约是原始Python代码的1000万倍。
在这种情况下,编译器可能优化了循环,只返回最终结果。这种加速对于真正的代码来说不是典型的,但是当替代方案是Python中的许多低级操作时,本练习无疑说明了Cython的强大功能。