Cython教程:如何加速Python

Python是一种功能强大的编程语言,易于学习且易于使用,但它并非总是运行速度最快的语言,尤其是在处理数学或统计信息时。 NumPy之类的第三方库包装了C库,可以显着提高某些操作的性能,但是有时您只需要直接在Python中使用C的原始速度和功能即可。

Cython的开发是为了使编写Python的C扩展变得更容易,并允许将现有的Python代码转换为C。此外,Cython允许将优化的代码与Python应用程序一起提供,而无需外部依赖。

[ 同样在InfoWorld上:3个主要的Python缺陷-及其解决方案 ]

在本教程中,我们将逐步完成将现有Python代码转换为Cython并将其用于生产应用程序所需的步骤。

相关视频:使用Cython加速Python

Cython示例

让我们从Cython的文档中获取一个简单的示例开始,该函数并不是非常有效的积分函数实现

def f(x):
    return x**2-x

def integrate_f(a, b, N):
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx

该代码易于阅读和理解,但是运行缓慢。 这是因为Python必须不断地在其自己的对象类型和计算机的原始数值类型之间来回转换。

现在考虑相同代码的Cython版本,并强调Cython的附加功能:

cdef f( double x):
    return x**2-x

def integrate_f( double a, double b, int N):
    cdef int i
    cdef double s, x, dx
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx

这些加法允许我们在整个代码中显式声明变量类型 ,以便Cython编译器可以将这些“修饰的”加法转换为C。

相关视频:Python如何简化编程

Python非常适合IT,可简化从系统自动化到机器学习等前沿领域的许多工作。

Cython语法

在传统的Python语法中找不到用于装饰Cython代码的关键字。 它们是专门为Cython开发的,因此装饰有它们的任何代码都不会像传统的Python程序那样运行。

这些是Cython语法的最常见元素:

变量类型

Cython中使用的某些变量类型是Python自身类型的回显,例如intfloatlong 。 其他Cython变量类型也可以在C中找到,例如charstruct ,以及类似unsigned long声明。 Cython特有的其他功能,例如bint ,它是Python True/False值的C级表示。

cdefcpdef函数类型

关键字cdef指示使用Cython或C类型。 它也用于定义函数,就像在Python中一样。

使用Python的def关键字用Cython编写的函数对其他Python代码可见,但是会导致性能下降。 使用cdef关键字的函数仅对其他Cython或C代码可见,但执行速度更快。 如果您的功能仅在Cython模块中内部调用,请使用cdef

第三个关键字cpdef提供与Python代码和C代码的兼容性,以这种方式C代码可以全速访问声明的函数。 这种便利性是有代价的,虽然: cpdef功能产生更多的代码,并略超过调用开销cdef

其他Cython关键字

Cython中的其他关键字可提供对Python所不具备的程序流和行为方面的控制:

  • gil nogil 这些是上下文管理器,用于描述需要( with gil: with nogil:或不需要( with nogil: )Python的Global Interpreter Lock或GIL的代码部分。 不调用Python API的C代码可以在nogil块中更快地运行,尤其是当它执行长时间运行的操作(例如从网络连接读取数据)时。
  • cimport 这将指导Cython导入C数据类型,函数,变量和扩展类型。 例如,使用NumPy的本机C模块的Cython应用程序使用cimport来访问这些功能。
  • include 这将一个Cython文件的源代码放在另一个文件中,就像在C语言中一样。请注意,Cython具有更复杂的方式在Cython文件之间共享声明,而不仅仅是include
  • ctypedef 用于引用外部C头文件中的类型定义。
  • externcdef一起使用,以引用其他模块中的C函数或变量。
  • public/api 用于在Cython模块中进行声明,其他C代码可以看到这些声明。
  • inline 用来表示给定的函数应该内联,或者为了快速起见,在使用时将其代码放置在调用函数的主体中。 例如,上面的代码示例中的f函数可以用inline修饰以减少其函数调用开销,因为它仅在一个地方使用。 (请注意,C编译器可能会自动执行自己的内联,但是inline使您可以明确指定是否应内联某些东西。)

不必事先了解所有Cython关键字。 Cython代码倾向于以增量方式编写-首先编写有效的Python代码,然后添加Cython装饰以加快速度。 因此,您可以根据需要选择Cython的扩展关键字语法。

编译Cython

现在我们对一个简单的Cython程序是什么样,以及为什么看起来会有所了解了,让我们逐步了解将Cython编译为工作二进制文件所需的步骤。

要构建一个有效的Cython程序,我们需要三件事:

  1. Python解释器。 如果可以,请使用最新发行版本。
  2. Cython程序包。 您可以通过pip软件包管理器将Cython添加到Python: pip install cython
  3. AC编译器。

如果您使用Microsoft Windows作为开发平台,则第3项可能会很棘手。 与Linux不同,Windows没有将C编译器作为标准组件。 为了解决这个问题,请获取Microsoft Visual Studio Community Edition的副本,其中包括Microsoft的C编译器,并且不收取任何费用。

请注意,在撰写本文时,Cython的最新发行版本是0.29.16,但是可以使用Beta版的Cython 3.0。 如果您使用pip install cython ,则会安装最新的非beta版本。 如果要试用Beta,请使用pip install cython>=3.0a1安装最新版本的Cython 3.0分支。 Cython的开发人员建议尽可能尝试Cython 3.0分支,因为在某些情况下,它会产生明显更快的代码。

Cython程序使用.pyx文件扩展名。 在新目录中,创建一个名为num.pyx的文件,该文件包含上面显示的Cython代码示例(“ A Cython示例”下的第二个代码示例)和一个名为main.py的文件,其中包含以下代码:

from num import integrate_f
print (integrate_f(1.0, 10.0, 2000))

这是一个常规的Python程序,将调用num.pyxintegrate_f函数。 Python代码将Cython代码视为“另一个”模块,因此,除了导入已编译的模块并运行其功能之外,您无需执行任何其他特殊操作。

最后,使用以下代码添加一个名为setup.py的文件:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

ext_modules = [
    Extension(
        r'num',
        [r'num.pyx']
    ),
]

setup(
    name='num',
    ext_modules=cythonize(ext_modules),
)

Python通常使用setup.py来安装与其关联的模块,也可以用于指示Python编译该模块的C扩展。 在这里,我们使用setup.py来编译Cython代码。

如果您使用的是Linux,并且已安装C编译器(通常是这种情况),则可以通过运行以下命令将.pyx文件编译为C:

python setup.py build_ext --inplace

如果您使用的是Microsoft Windows和Microsoft Visual Studio 2017或更高版本,则需要确保已在Python中安装了最新版本的setuptools (在撰写本文时为46.1.3版),然后该命令才能生效。 这样可以确保Python的构建工具将能够自动检测并使用您已安装的Visual Studio版本。

如果编译成功,您应该会在目录中看到新文件: num.c (由Cython生成的C文件)以及扩展名为.o的文件(在Linux上)或.pyd扩展的文件(在Windows上)。 那就是C文件被编译成的二进制文件。 您可能还会看到一个\build子目录,其中包含来自构建过程的工件。

运行python main.py ,您应该看到类似以下内容的响应:

283.297530375

这是由我们的纯Python代码调用的已编译积分函数的输出。 尝试使用传递给main.py传递给函数的参数,以查看输出如何变化。

请注意,每当对.pyx文件进行更改时,都需要重新编译它。 (您对常规Python代码所做的任何更改都会立即生效。)

生成的编译文件除了为其编译的Python版本外没有其他依赖项,因此可以捆绑到二进制wheel中 。 请注意,如果您在代码中引用其他库,例如NumPy(请参见下文),则需要将其作为应用程序需求的一部分提供。

如何使用Cython

既然您知道如何“ Cythonize”一段代码,下一步就是确定Python应用程序如何从Cython中受益。 您到底应该在哪里使用它?

为了获得最佳结果,请使用Cython优化以下类型的Python函数:

  1. 在紧密的循环中运行的功能,或在单个“热点”代码中需要大量处理时间的功能。
  2. 执行数字操作的函数。
  3. 与可以用纯C表示的对象一起工作的函数,例如基本数字类型,数组或结构,而不是列表,字典或元组之类的Python对象类型。

传统上,Python在循环和数字操作方面的效率不如其他非解释语言。 您装饰代码的次数越多,表明它应使用可以转换为C的基数值类型,则数字处理的速度就越快。

在Cython中使用Python对象类型本身并不是问题。 使用Python对象的Cython函数仍会编译,并且在性能不是首要考虑因素的情况下,Python对象可能更可取。 但是,任何使用Python对象的代码都会受到Python运行时性能的限制,因为Cython会生成代码来直接处理Python的API和ABI。

Cython优化的另一个值得关注的目标是直接与C库交互的 Python代码。 您可以跳过Python“包装器”代码并直接与库连接。

然而,用Cython 不会自动生成这些库中的正确调用接口。 您将需要Cython通过声明中的cdef extern from引用库头文件中的函数签名。 请注意,如果没有头文件,Cython会很宽容地允许您声明近似于原始头的外部函数签名。 但是为了安全起见,请尽可能使用原件。

Cython可以立即使用的一个外部C库是NumPy。 要利用Cython对NumPy数组的快速访问,请使用cimport numpy (可以选择使用as np来保持其命名空间不同),然后使用cdef语句声明NumPy变量,例如cdef np.arraynp.ndarray

Cython分析

改善应用程序性能的第一步是对其进行概要分析-生成有关执行期间花费时间的详细报告。 Python提供了用于生成代码配置文件的内置机制。 Cython不仅参与了这些机制,而且拥有自己的分析工具。

Python自己的探查器cProfile生成报告,以显示在给定的Python程序中哪些功能占用最多的时间。 默认情况下,Cython代码不会显示在那些报告中,但是您可以通过在.pyx文件顶部插入要包含在分析中的函数的编译器指令来对Cython代码进行分析:

# cython: profile=True

您还可以在Cython生成的C代码上启用逐行跟踪 ,但这会带来很多开销,因此默认情况下处于关闭状态。

请注意,性能分析会影响性能,因此请确保针对要交付生产的代码关闭性能分析。

Cython还可以生成代码报告,以指示将给定的.pyx文件中的多少转换为C,还有剩余的Python代码。 要查看实际效果,请在我们的示例中编辑setup.py文件,并在顶部添加以下两行:

import Cython.Compiler.Options
Cython.Compiler.Options.annotate = True

(或者,您可以在setup.py中使用指令来启用注释,但是上述方法通常更易于使用。)

删除项目中生成的.c文件,然后重新运行setup.py脚本以重新编译所有内容。 完成后,您应该在与您的.pyx文件名称相同的目录中看到一个HTML文件,在本例中为num.html 。 打开HTML文件,您将看到仍然依赖于Python的代码部分以黄色突出显示。 您可以单击黄色区域以查看Cython生成的基础C代码。

cython报告01 IDG

Cython代码报告。 黄色突出显示表示代码部分仍取决于Python运行时。

From: https://www.infoworld.com/article/3252209/cython-tutorial-how-to-speed-up-python.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值