Cython 与编译加速
问题
如何加速 Python 的 CPU 密集型代码?Cython、Numba、PyPy 各有什么特点?
答案
Cython
将 Python 代码编译为 C 扩展,通过静态类型声明大幅提速:
fast_math.pyx
# cython: boundscheck=False, wraparound=False
def fibonacci(int n):
cdef int a = 0, b = 1, i
for i in range(n):
a, b = b, a + b
return a
# 编译
cythonize -i fast_math.pyx
Numba(JIT 编译)
通过装饰器即时编译,无需修改代码结构:
from numba import njit
import numpy as np
@njit # Just-In-Time 编译
def monte_carlo_pi(n: int) -> float:
count = 0
for i in range(n):
x = np.random.random()
y = np.random.random()
if x * x + y * y <= 1.0:
count += 1
return 4.0 * count / n
# 首次调用触发编译,之后调用极快
result = monte_carlo_pi(10_000_000)
对比
| 方案 | 加速倍数 | 学习成本 | 适用场景 |
|---|---|---|---|
| Cython | 10-100x | 高(需学 Cython 语法) | 库开发、核心算法 |
| Numba | 10-100x | 低(加装饰器即可) | 数值计算、NumPy 操作 |
| PyPy | 2-10x | 零(替换解释器) | 整个程序加速 |
| C 扩展 | 100x+ | 极高 | 极致性能需求 |
PyPy
# 直接用 PyPy 运行现有代码
pypy3 myapp.py
PyPy 限制
PyPy 不支持 C 扩展(如 NumPy 的 C 部分),适合纯 Python 代码。科学计算项目不推荐。
常见面试问题
Q1: Numba 的原理?
答案:
Numba 使用 LLVM 编译器将带 @njit 装饰的 Python 函数编译为机器码。首次调用时触发编译(有延迟),之后直接执行机器码。支持 NumPy 数组操作,但不支持任意 Python 对象。
Q2: 什么时候该用 C 扩展?
答案:
当 Cython/Numba 无法满足且对性能要求极致时(如密码学、图像处理核心算法)。现代推荐使用 pybind11 或 PyO3(Rust)代替手写 CPython C API。