目录
Python C扩展的隐藏性能开销
Python 很慢,而像 Rust、C 或 C++ 这样的编译语言则很快。因此,当你的应用程序运行得太慢时,将部分代码重写为编译扩展似乎是加速的自然选择。
不幸的是,编译扩展有时实际上比等效的 Python 代码更慢。即使它们更快,性能提升也可能远低于你的预期,这是由于两个因素导致的隐藏开销:
- 函数调用开销。
- 序列化/反序列化开销。
让我们看看这些隐藏的性能开销来自哪里,然后看看如何绕过它们。
问题 #1:调用开销
我们面临的第一个性能开销是函数调用。让我们用 Cython 编写一个函数,Cython 是一种编译为 C 的 Python 变体语言,看看运行它需要多长时间。
以下是 Cython 函数:
def do_nothing():
pass
我们可以使用 IPython 的 %timeit
魔法函数来测量性能:
In [1]: from overhead_cython import do_nothing
In [2]: %timeit do_nothing()
30 ns ± 0.0352 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [3]: def py_do_nothing(): pass
In [4]: %timeit py_do_nothing()
62.5 ns ± 0.114 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
调用 Cython 函数比调用 Python 函数更快,这是事实。但即使是 30 纳秒,对于编译语言来说也相当慢:相比之下,C 函数调用另一个 C 函数可能只需要 3 纳秒,如果内联的话甚至更少。
问题 #2:(反)序列化开销
除了调用扩展的开销外,还有将参数传入函数并返回结果的开销。Python 表示对象的方式与 C 或 Rust 不同,因此需要进行转换。转换代码还需要错误处理以防失败。
结果是更多的开销。例如,这里有一个 Cython 函数,它接受一个 Python 整数列表,对它们求和并返回结果:
def sum(values):
cdef long result = 0
cdef long v
for v in values:
result += v
return result
我们可以比较与 Python 的性能:
In [1]: from example_cython import sum as cython_sum
In [2]: l = list(ra