Python的执行效率一直是被诟病的短板,尤其是在数值计算、循环处理这类CPU密集型场景中,解释执行的开销会被无限放大。Cython提供了一个务实的解决思路:它允许你用接近Python的语法写代码,再将其编译成C扩展模块,最终以机器码的速度运行。在Ubuntu系统上,这套流程非常成熟,只需要系统自带的gcc编译器和几个Python工具库就能跑通整个链路。本文将从环境搭建讲起,逐步演示如何将一段普通Python代码改造并编译,最后给出性能对比与进阶优化建议。

一、环境准备与Cython安装
Ubuntu系统默认带有gcc,但编译Cython扩展还需要Python的头文件。如果系统中没有安装,编译阶段会报出Python.h: No such file or directory这样的错误。执行下面的命令可以一次性把依赖装齐:
sudo apt update sudo apt install -y build-essential python3-dev pip3 install cython numpy
安装完成后,可以通过命令行验证版本:
cython --version # 输出类似:Cython version 3.0.x
build-essential提供了gcc、g++以及make等编译工具链,python3-dev则包含Python.h等头文件,这两者是编译C扩展的硬性前提。Cython本身是一个纯Python包,通过pip安装即可,无需任何系统级配置。如果你的项目使用了虚拟环境,建议在虚拟环境内安装,这样编译出的扩展模块会自动匹配当前环境的Python版本。
二、从一个慢函数开始:普通Python版本
为了直观感受Cython的效果,先准备一个典型的CPU密集型任务:计算某个范围内所有素数的和。这个任务包含大量循环和取模运算,是Python解释器最不擅长的场景。先写一个纯Python版本作为基准:
# pure_python.py
def is_prime(n):
if n < 2:
return False
i = 2
while i * i <= n:
if n % i == 0:
return False
i += 1
return True
def sum_primes(limit):
total = 0
for n in range(limit):
if is_prime(n):
total += n
return total在笔者的机器上(i5处理器),调用sum_primes(2000000)大约需要8秒左右。这个数字会因硬件而异,但量级上具有代表性。代码逻辑没有任何问题,慢的原因在于Python的动态类型机制:每一次循环中的加法、比较操作,解释器都要执行类型检查、对象装箱拆箱,而这些底层工作在C语言中几乎是零成本的。
三、改写为Cython并编译
接下来创建一个primes_cy.pyx文件,把上述代码几乎原样搬进去,只加上类型声明。这是Cython的核心用法:用cdef关键字声明C类型的变量,编译后这些变量就变成真正的C变量,不再走Python对象那一套机制:
# primes_cy.pyx
def is_prime(int n):
cdef int i = 2
while i * i <= n:
if n % i == 0:
return False
i += 1
return True
def sum_primes(int limit):
cdef long total = 0
cdef int n
for n in range(limit):
if is_prime(n):
total += n
return total注意函数参数也标注了类型。当函数参数声明为int时,Cython会在C层面直接处理整数,省去了Python对象与C int之间的转换。然后编写一个setup.py用于编译:
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("primes_cy.pyx", compiler_directives={
"language_level": 3
})
)在终端执行编译命令:
python3 setup.py build_ext --inplace
编译成功后,当前目录会生成一个primes_cy.cpython-3xx-x86_64-linux-gnu.so文件(Windows上是.pyd)。这个.so文件就是可以被Python直接导入的原生扩展模块。在Python中导入并调用:
import primes_cy print(primes_cy.sum_primes(2000000))
实测同样的输入,Cython版本耗时约0.25秒,相比纯Python的8秒提升了三十倍以上。这个提升幅度并非夸张宣传,而是消除了动态类型开销后的正常水平。对于计算密集型代码,只要类型声明写得足够充分,接近C语言的性能完全可以达到。
四、进阶技巧与常见坑点
掌握基本流程后,还有几个能进一步榨取性能的点值得了解。
1. 使用cpdef让函数既可导入又可内联。def定义的函数参数走Python调用协议,cdef定义的函数只能在Cython内部调用且无法被Python导入。cpdef则兼顾两者:对外暴露Python接口,对内在Cython模块内部调用时走C调用,可以被内联优化。把上面例子中的def is_prime改成cpdef bint is_prime(int n),性能还能再提升一截,因为内层循环的函数调用开销被消除了。
2. 配合numpy处理数组。如果代码操作的是numpy数组,记得使用cimport numpy并配合类型化的memoryview,这样数组访问会编译成直接的C指针操作:
# arr_cy.pyx
import numpy as np
cimport numpy as cnp
def sum_array(double[:] arr):
cdef double total = 0.0
cdef int i
cdef int n = arr.shape[0]
for i in range(n):
total += arr[i]
return totaldouble[:]这种写法就是类型化memoryview,比直接索引numpy数组快得多,因为它绕开了numpy的Python层索引逻辑。
3. 开启编译优化选项。默认情况下gcc以较低优化级别编译,可以在setup.py中显式指定额外参数,例如传入extra_compile_args=["-O3"],让编译器进行更激度的优化。对数值计算代码,这一步通常能带来百分之十几的额外提升。
4. 常见坑点。一是类型声明不是越多越好,涉及字符串、字典等复杂Python对象时强行声明C类型反而别扭,只对热点循环中的数值变量加类型即可;二是每次修改pyx文件后必须重新编译,否则导入的还是旧版本;三是用cython -a primes_cy.pyx可以生成带高亮的HTML分析报告,黄色越多的行表示与Python交互越频繁,优化的重点就是把黄色部分变白。
五、总结
Cython在Ubuntu上的使用门槛并不高:装好编译依赖,写好pyx文件和setup.py,一条命令完成编译。它的价值在于保持了Python的开发体验,同时能对关键热点代码做C级别的优化。实践中的推荐策略是,先用性能分析工具定位程序中真正耗时的10%代码,只把这部分改写成Cython,其余代码维持纯Python,这样既能获得大幅提速,又不会增加太多维护成本。对于希望深入的朋友,可以进一步研究@cython.boundscheck(False)、nogil以及与OpenMP结合的并行化写法,性能上限还有很大的挖掘空间。
CythonPython性能优化Ubuntu修改时间:2026-09-16 03:22:38