索引单个数组元素
这是 Cython 可以真正帮助您的主要代码类型。在 Python 中索引单个元素(例如an = a[n])可能是一个相当慢的操作。部分原因是 Python 不是一种非常快的语言,因此在循环中多次运行 Python 代码可能会很慢,部分原因是该数组存储为 C 浮点数的紧凑数组,但索引操作需要返回一个 Python目的。因此,索引 Numpy 数组需要分配新的 Python 对象。
在 Cython 中,您可以将数组声明为 typed memoryviews 或 np.ndarray。 (类型化的内存视图是更现代的方法,您通常应该更喜欢它们)。这样做允许您直接访问紧密封装的 C 数组并检索 C 值,而无需创建 Python 对象。
cython.boundscheck 和 cython.wraparound 指令对于进一步加快索引速度非常值得(但请记住,它们确实删除了有用的功能,因此在使用它们之前三思)。
vs 向量化
很多时候,Numpy 数组上的循环可以写成向量化操作——一次作用于整个数组。像这样编写 Python+Numpy 代码通常是个好主意。如果您有多个链式矢量化操作,有时值得将其显式编写为 Cython 循环以避免分配中间数组。
另外,鲜为人知的 Cython Pythran backend 将一组矢量化 Numpy 操作转换为优化的 C++ 代码。
索引数组切片
在 Cython 中不是问题,但通常不会单独让您显着加速。
调用 Numpy 函数
例如last = np.sin(an)
这些需要 Python 调用,因此 Cython 通常无法加速这些 - 它无法查看 Numpy 函数的内容。
然而,这里的操作是针对单个值,而不是针对 Numpy 数组。在这种情况下,我们可以使用 C 标准库中的sin,这将比 Python 函数调用快得多。你会做from libc.math cimport sin 并调用sin 而不是np.sin。
Numba 是一种替代 Python 加速器,它对 Numpy 函数具有更好的可见性,通常可以在不更改的情况下进行优化。
数组分配
例如transformed_a = np.zeros_like(a).
这只是一个 Numpy 函数调用,因此 Cython 无法加速它。如果它只是一个要返回给 Python 的中间值,那么您可以考虑在堆栈上放置一个固定大小的 C 数组
cdef double transformed_a[10] # note - you must know the size at compile-time
或者通过 C 函数 malloc 分配它们(记住 free 它)。或者使用 Cython 的 cython.view.array(它仍然是一个 Python 对象,但可以更快一点)。
全数组算术
例如transformed_a * b,将 transformed_a 和 b 逐个元素相乘。
Cython 在这里帮不了你——它只是一个伪装的函数调用(尽管 Pythran+Cython 可能有一些好处)。对于大型数组,这种操作在 Numpy 中非常有效,所以不要想太多。
请注意,没有为 Cython 类型的内存视图定义整个数组操作,因此您需要执行 np.asarray(memview) 才能将它们返回到 Numpy 数组。这通常不需要副本并且速度很快。
对于这样的一些操作,您可以使用BLAS 和LAPACK 函数(它们是数组和矩阵操作的快速C 实现)。 Scipy 为他们 (https://docs.scipy.org/doc/scipy/reference/linalg.cython_blas.html) 提供了一个 Cython 界面供他们使用。与自然 Python 代码相比,它们使用起来稍微复杂一些。
说明性示例
为了完整起见,我会这样写:
import numpy as np
from libc.math cimport sin
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def some_func(double[::1] a, b):
cdef double[::1] transformed_a = np.zeros_like(a)
cdef double last = 0
cdef double an, delta
cdef Py_ssize_t n
for n in range(1, a.shape[0]):
an = a[n]
if an > 0:
delta = an - a[n-1]
transformed_a[n] = delta*last
else:
last = sin(an)
return np.asarray(transformed_a) * b
快了 10 倍多一点。
cython -a 在这里很有帮助 - 它会生成一个带注释的 HTML 文件,显示哪些行包含与 Python 的大量交互。