将Cython添加到SciPy

就像写在 Cython website

Cython是一个针对Python编程语言和扩展Cython编程语言(基于Pyrex)的优化静电编译器。它使编写Python的C扩展与编写Python本身一样简单。

如果您的代码目前在Python中执行了大量循环,那么使用Cython编译可能会让它受益。本文档旨在进行非常简短的介绍:仅足以了解如何将Cython与SciPy一起使用。编译代码后,您可以通过查看 Cython documentation

为了让SciPy使用Cython编译您的代码,您只需要做两件事:

  1. 将您的代码包含在包含 .pyx 扩展名,而不是 .py 分机。所有带有 .pyx 扩展名由Cython自动转换为 .c 在构建SciPy时创建文件。

  2. 从此添加扩展名 .c 文件添加到代码所在的子包的配置中。通常,这非常简单:在子包的 setup.py 文件。添加为扩展后, .c 构建SciPy时,代码将由您的C编译器编译为机器码。

示例

scipy.optimize._linprog_rs.py 包含修改后的单纯形方法的实现。 scipy.optimize.linprog 。改进后的单纯形法对矩阵进行了大量的初等行运算,是一种很自然的元素化候选方法。

请注意, scipy/optimize/_linprog_rs.py 导入 BGLULU 来自以下位置的类 ._bglu_dense 就像它们是普通的Python类一样。但他们不是。 BGLULU 中是否定义了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 将被编译为机器码,并且我们将能够导入 LUBGLU 扩展中的类 _bglu_dense

锻炼身体

See a video run-through of this exercise: Cythonizing SciPy Code

  1. 更新Cython并创建新分支(例如, git checkout -b cython_test ),其中对SciPy进行了一些试验性更改

  2. 将一些简单的Python代码添加到 .py 文件中的 /scipy/optimize 目录,比方说 /scipy/optimize/mypython.py 。例如:

    def myfun():
        i = 1
        while i < 10000000:
            i += 1
        return i
    
  3. 让我们看看这个纯Python循环需要多长时间,以便我们可以比较Cython的性能。例如,在Spyder的IPython控制台中:

    from scipy.optimize.mypython import myfun
    %timeit myfun()
    

    我得到的信息是这样的:

    715 ms ± 10.7 ms per loop
    
  4. 省下你的 .py 文件添加到 .pyx 文件,例如mycython.pyx

  5. 建立SciPy。请注意,一个 .c 文件已添加到 /scipy/optimize 目录。

  6. 在类似行附近的某个位置,从您的 .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
    
  7. 重建本网站。请注意,一个 .so 文件已添加到 /scipy/optimize 目录。

  8. 计时:

    from scipy.optimize.mycython import myfun
    %timeit myfun()
    

    我得到的信息是这样的:

    359 ms ± 6.98 ms per loop
    

    Cython将纯Python代码的速度提高了约2倍。

  9. 这对事情的计划来说并没有多大的改善。要了解原因,让Cython创建代码的“带注释”版本以显示瓶颈是很有帮助的。在终端窗口中,在 .pyx 文件与 -a 标志:

    cython -a scipy/optimize/mycython.pyx
    

    请注意,这将创建一个新的 .html 文件中的 /scipy/optimize 目录。打开 .html 任何浏览器中的文件。

  10. 文件中以黄色突出显示的行表示编译后的代码与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交互已经消失。

  11. 重新构建SciPy,打开全新的IPython控制台,然后 %timeit

from scipy.optimize.mycython import myfun
%timeit myfun()

我得到的信息是这样的: 68.6 ns ± 1.95 ns per loop 。Cython代码的运行速度大约是原始Python代码的1000万倍。

在这种情况下,编译器可能优化了循环,只返回最终结果。这种加速对于真正的代码来说不是典型的,但是当替代方案是Python中的许多低级操作时,本练习无疑说明了Cython的强大功能。