Python Cython教程--将你的代码速度提高1000倍

2,444 阅读7分钟

作为最流行的语言之一,Python不断地与其他流行语言如C/C++进行比较和对照。对Python最常见的抱怨是它有多慢。你经常会看到基准测试显示C/C++比Python快10倍(或更多)。在今天的教程中,我们将探讨 "Cython",它将使我们在性能方面大大缩小Python与其他语言的差距。

但首先,什么是Cython?


什么是Cython?

Cython是Python编程语言的一个超级集合,它是Python和C/C++之间的一个中间人。简而言之,Cython为我们提供了一种将Python代码编译成C/C++的方法。因此,它并不是真正地直接优化Python,而是将其编译成一种运行速度更快的低层次语言

这当然意味着Cython永远不可能比C/C++快,相反,由于开销,以及通常代码中会保留一些Python元素(就像只把某些部分转换为C/C++一样),它的速度会慢一点。

但它仍然是一个很好的选择,因为它允许我们使用Python写出快速的代码,而没有太多的麻烦。

关于Cython的另一个有趣的小事,主要的Python库,如NumPy和Pandas已经使用Cython来提高性能。这说明了Cython在业界的应用程度,也应该让你放心,学习Cython真的很值得。


Cython是如何提高性能的?

说Cython只是将Python代码编译成C/C++,有点过于简单化了。作为程序员,我们应该知道Cython到底是如何实现这些性能提升的。

简单地说,Cython应用了多种优化方法。其中大部分与 "键入信息 "有关。这是因为Python是一种动态类型的语言,这意味着变量的类型可以在运行时改变。然而,这是以性能为代价的,在某些情况下会导致性能受到巨大的打击。

Cython为我们提供了为Python变量定义静态类型的能力。因此,我们现在不需要写:

x = 0

我们现在写

cdef int x = 0

就像静态类型语言一样,如果我们试图给Python分配除int 以外的任何东西,这将引发一个错误。

另一个优化是在Cython最初编译Python时进行的。这产生了一个轻微的性能优势,即使你不使用任何Cython语法。其他优化可以从使用C/C++兼容的对象中获得,比如Numpy的数组。

我们不会一次就把所有的各种优化都加进去,而是一次一次地做。这样我们就能监控每一步的性能是如何被影响的。这将帮助你了解哪些优化有更大的作用,最重要的是你将了解Cython是如何提高性能的。


使用Cython编译一个Python程序

在这里,我们有一些代码来生成Python中的斐波那契数列。让我们将这段代码所在的文件命名为 "program1.py"。在本教程的后面,我们将探讨更多的程序。

def fib(n):
    n1, n2 = 0, 1

    for i in range(1, n + 1):
        temp = n1 + n2
        n1 = n2
        n2 = temp
    return n2

我们现在不会做任何改动。我们先来探讨一下如何用Cython来编译这个程序,看看这对性能是否有影响。

设置Cython可能相当烦人,但这将是值得的。

  1. 你需要做的第一件事是安装Cython,使用pip install cython 或任何同等方法。
  2. 其次,复制一个你的Python文件,并将扩展名和名称稍微改为"program1_cy.pyx"。你也可以选择使用相同的名字,但我们这样做是为了基准测试的目的,你将在后面看到。
  3. 创建一个名为setup.py的文件,在里面粘贴以下代码。
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("program1.pyx",
    compiler_directives = { "language_level" : "3"}),
)

第一个参数是用来编译C/C++的文件名,第二个参数定义了我们是使用Python 2还是Python 3。

现在运行以下命令。(确保这一切都发生在同一个目录下)

python setup.py build_ext --inplace

这应该会生成所需的文件。你会注意到一个构建文件夹、一个.so (共享库) 和一个.c.cpp 文件。我们的代码现在已经准备好并被编译。让我们试着运行它。在一个新的Python文件中,我们运行下面的代码会给我们的输出。

import program1_cy

print(program1_cy.fib(100))

它将给我们一个值,573147844013817084101 ,这是正确的输出。但是我们怎么知道这在Cython中是否比在Pure Python中快呢?让我们做一个基准测试。


基准测试#1

这是本Python Cython教程的第一个基准测试。我们将建立一个名为test.py 的新文件,在那里我们将编写以下代码。我们将使用timeit库来做基准测试

import timeit

python= timeit.timeit('program1.fib(10000)',
       setup='import program1',number=100)
cython= timeit.timeit('program1_cy.fib(10000)', 
       setup='import program1_cy',number=100)

print("Python Time: ", python)
print("Cython Time: ", cython)
print(f"{python/cython}x times faster")

我们做的第一个基准测试将是10000,并且将各做100次。(我们进行100次迭代,以消除异常值,使我们的结果更加准确)。

Python Time:  0.3353964
Cython Time:  0.21417430000000004
1.565997414255585x times faster

在这里,我们已经可以看到超过50%的改进。让我们再运行一次。

Python Time:  0.2971565
Cython Time:  0.3253751
0.9132736340303853x times faster

这里我们看到Cython输了。这种情况有时会随机发生,但你会注意到Cython赢得了大多数测试。

让我们增加Fibonacci的nth 数量。这将使结果向Cython倾斜。

对于第100000期。

Python Time:  3.9538857000000003
Cython Time:  1.1979654000000002
3.300500749019963x times faster

在这里,我们看到Cython的速度快了三倍以上而这是在我们这边没有任何改动的情况下。


用Cython添加类型信息

现在我们开始用Cython向Python添加类型信息。通常Python有def 关键字,但是Cython引入了两个新的关键字,叫做cdefcpdef

cdef 当使用这个声明时,只生成一个C版本的函数/对象。

cpdef 声明的变量/函数可以在Python和C中使用。有一些例外情况,例如使用C指针时,但我们将在后面的教程中讨论。

那么我们要使用哪一种呢?好吧,从Cython 3.0开始, cpdef 变量不再被支持(因为它们的行为与cdef 变量没有区别)。所以我们将使用cpdef 来表示函数,而使用cdef 来表示变量。


现在让我们添加一些类型信息。(别忘了为函数参数添加类型信息)

cpdef int fib(int n):
    cdef int n1 = 0
    cdef int n2 = 1 
    cdef int temp, i

    for i in range(1, n + 1):
        temp = n1 + n2
        n1 = n2
        n2 = temp
    return n2

下面是我们更新的代码。我们给函数的返回类型是int ,并将所有其他变量也声明为int 。这里的好处是,Python 不需要不断地问自己,"这个变量的类型是什么?"。

这似乎是一个非常小的操作,而且确实如此!但是当你不得不不断地问自己 "这个变量的类型是什么?但是当你必须不断地查询一个变量的类型1000000次时会发生什么呢?由于我们在for循环里面有不止一个变量,你可以把这个数字乘以4-5。


基准# 2

那么,这给我们带来了多大的性能提升呢?

对于第1000个术语:(记得先重新编译)。

Python Time:  0.0008732000000000045
Cython Time:  6.8000000000012495e-06
128.4117647058594x times faster

哇!快了128倍。让我们来试试第10000项的情况。

Python Time:  0.0311598
Cython Time:  5.9000000000003494e-05
528.1322033897992x times faster

快了528倍!更不可思议的是!现在最后一次,对第100000个词。

Python Time:  4.3451835
Cython Time:  0.0015122000000005187
2873.418529294081x times faster

这个结果是我们教程的主要部分,向你展示在Python中使用Cython可以将计算速度提高多少。我们成功地编写了比原来快2000倍的代码。