Python 太慢?试试这个神仙库,让你的代码速度飙升 80 倍!

B站影视 内地电影 2025-08-23 19:37 3

摘要:在当今的编程世界里,Python 以其简洁的语法和强大的库生态系统,成为了快速原型设计和应用开发的首选语言。然而,许多开发者,尤其是那些从事数据科学和机器学习的工程师们,常常会遇到一个令人头疼的问题:Python 的运行速度不够快。对于需要进行大量计算密集型操

Python VS Cython

在当今的编程世界里,Python 以其简洁的语法和强大的库生态系统,成为了快速原型设计和应用开发的首选语言。然而,许多开发者,尤其是那些从事数据科学和机器学习的工程师们,常常会遇到一个令人头疼的问题:Python 的运行速度不够快。对于需要进行大量计算密集型操作,比如矩阵乘法和图像处理等任务,Python 的慢速有时会成为一个瓶颈。

尽管 Python 的内部机制一直在不断演进,通过引入新特性或重写现有功能来提升性能,但其固有的全局解释器锁(Global Interpreter Lock, GIL)在很多时候阻碍了这些努力。当然,也有一些外部库被开发出来,以弥合 Python 与 Java 等编译型语言之间的性能差距。其中,最广为人知的可能是numpy库,它使用 C 语言实现,旨在支持多核 CPU 和超快的数值及数组处理。另一个非常有效的库是Numba,它利用即时(JIT)编译器,在运行时将部分 Python 和 NumPy 代码转换为快速的机器码,特别适用于加速数值和科学计算任务。

今天,我们要重点介绍一个同样能大幅提升 Python 程序运行速度的强大工具——Cython。它号称能将你的 Python 代码速度提升惊人的 80 倍。接下来,我将详细为你揭示 Cython 的工作原理,并通过几个实际的例子,手把手教你如何应用它,无论是在 Jupyter Notebook 中还是在常规的 Python 脚本里,都能让你的代码飞驰起来。

Cython是 Python 语言的一个超集,其设计初衷就是为了让开发者能够用接近 Python 的语法编写出具有 C 语言性能的代码。它的核心工作流程是,将 Python 代码转换成 C 代码,然后再将 C 代码编译成共享库。这些共享库可以像普通的 Python 模块一样被导入和使用,从而让你的程序在享受 C 语言性能优势的同时,依然保持 Python 代码的易读性。

简单来说,Cython 就像一座桥梁,连接了 Python 的开发效率和 C 语言的执行速度。通过它,你可以在 Python 代码中进行类型声明,这些声明让 Cython 编译器能够更高效地将代码转换成优化的 C 代码。这种方法在处理那些性能瓶颈代码时,能带来巨大的性能提升。

如果你习惯在 Jupyter Notebook 中进行开发,那么使用 Cython 会非常简单。我将通过一个简单的四步流程,来演示如何将一个普通的 Python 函数用 Cython 进行加速。

首先,我们需要一个基准,也就是我们用标准 Python 编写的原始函数,并测量它的运行时间。这将作为我们后续性能提升的参照。

我们来编写一个简单的双重循环函数slow_sum_of_squares,用于计算两个嵌套循环中i*i + j*j的和。这个函数在处理大数据量时会比较耗时,非常适合用来测试性能提升。

# slow_sum_of_squares.pyimport timeit# 定义标准Python函数def slow_sum_of_squares(n): total = 0 for i in range(n): for j in range(n): total += i * i + j * j return total# 对Python函数进行基准测试print("Python function execution time:")print("timeit:", timeit.timeit( lambda: slow_sum_of_squares(20000), number=1))

在我的高性能系统上,这段代码的执行时间约为 13.13 秒。这是一个相当可观的数字,也为我们接下来的 Cython 优化留下了巨大的空间。

在 Jupyter Notebook 中,使用 Cython 非常方便。你只需要在 Notebook 的第一个单元格中加载 Cython 扩展,它就像一个魔法开关,开启了 Cython 的功能。

接下来,在任何包含你希望用 Cython 运行的 Python 代码的单元格中,添加%%cython魔法命令。这个命令告诉 Jupyter,接下来的代码应该由 Cython 进行编译和执行。

Cython 的关键在于类型化。为了获得 C 语言级别的性能,你需要对函数中的参数和变量进行显式类型声明。对于函数参数,你需要在函数定义时直接指定类型,例如def fast_sum_of_squares(int n):。对于函数内部的变量,你需要使用cdef指令来声明它们的类型,例如cdef int total = 0。

经过 Cython 化改造后,我们的求和函数变成了这样:

%%cythondef fast_sum_of_squares(int n): cdef int total = 0 cdef int i, j for i in range(n): for j in range(n): total += i * i + j * j return totalimport timeitprint("Cython function execution time:")print("timeit:", timeit.timeit( lambda: fast_sum_of_squares(20000), number=1))

在我的系统上,这段 Cython 代码的执行时间仅为 0.158 秒。相比于原始的 Python 代码,这实现了超过 80 倍的惊人加速!这个结果足以证明 Cython 在处理循环等计算密集型任务时的巨大优势。

为了更全面地展示 Cython 的威力,我们再来看两个更复杂的例子:蒙特卡洛模拟和图像卷积操作。这两个任务都属于计算密集型,是 Cython 大显身手的好地方。

蒙特卡洛模拟是一种利用随机过程来估计系统属性的方法,它需要大量的迭代计算,非常耗费性能。我们用它来计算 π 值。

基本原理是:在一个边长为 1 的正方形内,内切一个半径为 1 的四分之一圆。我们向正方形内随机投掷大量的点,落在四分之一圆内的点数与总点数的比例,会随着点数趋近于无穷大而趋近于 π/4。

下面是原始的 Python 代码,用random.uniform生成随机点:

import RANDomimport timedef monte_carlo_pi(num_samples): inside_circle = 0 for _ in range(num_samples): x = random.uniform(0, 1) y = random.uniform(0, 1) if (x**2) + (y**2)

在我的系统上,执行时间约为 20.67 秒。

现在,我们用 Cython 来优化这段代码。除了添加类型声明外,我们还使用cimport命令从 C 标准库中导入了优化的随机数生成函数rand和RAND_MAX,替代了 Python 的random.uniform,这进一步提升了性能。

%%cythonimport cythonimport randomfrom libc.stdlib cimport rand, RAND_MAX@cython.boundscheck(False)@cython.wraparound(False)def monte_carlo_pi(int num_samples): cdef int inside_circle = 0 cdef int i cdef double x, y for i in range(num_samples): x = rand /RAND_MAX y = rand /RAND_MAX if (x**2) + (y**2)

这段 Cython 代码的执行时间仅为 1.99 秒,实现了约 10 倍的加速。值得一提的是,cimport是 Cython 的一个关键字,专门用于导入 C 语言的函数、变量、常量和类型,这在需要调用底层 C 库时非常有用。

图像处理中的图像卷积是一种常见的操作,我们用它来给一张稍微模糊的图片进行锐化。

下面是原始的 Python 代码,使用scipy库中的convolve2d函数进行卷积操作。

from PIL import Imageimport numpy as npfrom scipy.signal import convolve2dimport timedef sharpen_image_color(image): start_time = time.time image = image.convert('RGB') kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) image_array = np.array(image) sharpened_array = np.zeros_like(image_array) for i in range(3): channel = image_array[:, :, i] convolved_channel = convolve2d(channel, kernel, mode='same', boundary='wrap') convolved_channel = np.clip(convolved_channel, 0, 255) sharpened_array[:, :, i] = convolved_channel.astype(np.uint8) sharpened_image = Image.fromarray(sharpened_array) duration = time.time - start_time print(f"Processing time: {duration:.4f} seconds") return sharpened_image

这段 Python 代码的执行时间约为 0.0317 秒。

现在,我们来看 Cython 的实现。在这个例子中,我们没有使用scipy库,而是直接在 Cython 中用嵌套循环实现了卷积操作。这再次强调了 Cython 在处理底层循环时的优势。我们同样添加了类型声明,并使用了@cython.boundscheck(False)和@cython.wraparound(False)装饰器来禁用边界检查和负索引包装,进一步提升了性能。

%%cython# cython: language_level=3# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSIONimport numpy as npcimport numpy as npimport cython@cython.boundscheck(False)@cython.wraparound(False)def sharpen_image_cython(np.ndarray[np.uint8_t, ndim=3] image_array): cdef int kernel[3][3] kernel[0][0] = 0 kernel[0][1] = -1 kernel[0][2] = 0 kernel[1][0] = -1 kernel[1][1] = 5 kernel[1][2] = -1 kernel[2][0] = 0 kernel[2][1] = -1 kernel[2][2] = 0 cdef int height = image_array.shape[0] cdef int width = image_array.shape[1] cdef int channel, i, j, ki, kj cdef int value cdef np.ndarray[np.uint8_t, ndim=3] sharpened_array = np.zeros_like(image_array) for channel in range(3): for i in range(1, height - 1): for j in range(1, width - 1): value = 0 for ki in range(-1, 2): for kj in range(-1, 2): value += kernel[ki + 1][kj + 1] * image_array[i + ki, j + kj, channel] sharpened_array[i, j, channel] = min(max(value, 0), 255) return sharpened_array

经过 Cython 处理后,这段代码的执行时间仅为 0.0024 秒,比 Python 版本快了近 13 倍。这再次证明了 Cython 在处理图像数据等大规模数组操作时的强大能力。

虽然 Jupyter Notebook 是入门 Cython 的绝佳环境,但大多数实际项目中的 Python 代码都以.py文件的形式存在,并通过命令行运行。在这种情况下,%load_ext和%%cython这些 Notebook 魔法命令就无法使用了。

不过,别担心,在常规 Python 脚本中调用 Cython 也同样简单,只需要多几个步骤。我将以我们第一个sum_of_squares例子为例,来展示这个过程。

首先,你需要将 Cython 代码放入一个独立的.pyx文件中,而不是在 Python 文件中。.pyx是 Cython 的扩展名,它告诉编译器这是一个 Cython 文件。

# sum_of_squares.pyxdef fast_sum_of_squares(int n): cdef int total = 0 cdef int i, j for i in range(n): for j in range(n): total += i * i + j * j return total

注意,我们移除了%%cython指令和计时代码,因为这些将在主 Python 脚本中处理。

接下来,你需要创建一个setup.py文件,这个文件负责告诉 Python 如何编译你的.pyx文件。

# setup.pyfrom setuptools import setupfrom Cython.Build import cythonizesetup( name="cython-test", ext_modules=cythonize("sum_of_squares.pyx", language_level=3), py_modules=["sum_of_squares"], zip_safe=False,)

这个setup.py文件使用了setuptools和Cython.Build中的cythonize函数。cythonize会找到我们的sum_of_squares.pyx文件,并将其编译成一个 Python 可以导入的 C 扩展模块。

在命令行中,运行以下命令来执行setup.py文件进行编译。

$ python setup.py build_ext --inplace

这个命令会生成一个 C 文件,然后将其编译成一个共享库文件(例如在 Linux 上是.so文件),该文件与你的.pyx文件位于同一目录下。

最后,我们创建一个常规的 Python 文件(比如main.py),在这个文件中像导入普通 Python 模块一样导入我们刚刚编译好的 Cython 模块,并调用其中的函数。

# main.pyimport time, timeitfrom sum_of_squares import fast_sum_of_squaresstart = time.timeresult = fast_sum_of_squares(20000)print("timeit:", timeit.timeit( lambda: fast_sum_of_squares(20000), number=1))

运行python main.py,你将看到 Cython 版本代码的执行时间,其结果与在 Jupyter Notebook 中运行时非常接近,同样实现了显著的加速。

通过这几个例子,我们充分展示了 Cython 在提升 Python 代码运行速度方面的强大能力。尽管它在首次使用时可能需要一些额外的学习成本,比如了解类型声明和编译流程,但所带来的性能提升是巨大的。在我们的三个例子中,我们分别实现了 80 倍、10 倍和 13 倍的加速,这足以说明 Cython 的有效性。

Cython的优势在于:

性能卓越:它能够将 Python 代码转换为 C 代码,利用 C 语言的执行速度。语法友好:它保留了大部分 Python 的语法,让开发者能够用熟悉的语言编写高性能代码。生态融合:它能与 NumPy 等现有 Python 库无缝集成,甚至可以通过cimport调用 C 标准库,进一步优化性能。

无论你是数据科学家、机器学习工程师,还是任何需要处理计算密集型任务的 Python 开发者,Cython 都值得你深入学习和尝试。通过将代码中的性能瓶颈部分用 Cython 进行重写和编译,你可以轻松地让你的程序运行得更快、更高效,从而将更多的精力投入到解决实际问题上,而不是等待代码运行。

来源:高效码农

相关推荐