什么是Cython?以C语言的速度运行的Python


什么是Cython?以C语言的速度运行的Python

Python以其最方便、装备最丰富、最实用的编程语言之一而闻名。执行速度呢?

进入Cython。Cython语言是Python的超集,可以编译成C语言,产生的性能提升从百分之几到几个数量级不等,这取决于手头的任务。对于受Python原生对象类型约束的工作,其速度提升不会很大。但是对于数字操作,或者任何不涉及Python自身内部的操作,其收益可能是巨大的。

[ Also on InfoWorld: How to use asyncio in Python ]

使用 Cython,你可以避开许多 Python 的原生限制或完全超越它们--而不必放弃 Python 的易用性和便利性。在这篇文章中,我们将了解Cython背后的基本概念,并创建一个简单的Python应用程序,使用Cython来加速其一个功能。使用 Cython 加速 Python 将 Python 编译成 C

Python 代码可以直接调用 C 模块。这些C模块可以是通用的C库,也可以是为与Python一起工作而专门建立的库。Cython生成了第二种模块。与Python内部对话的C库,可以与现有的Python代码捆绑在一起。

通过设计,Cython代码看起来非常像Python代码。如果你给Cython编译器提供一个Python程序(Python 2.x和Python 3.x都支持),Cython将接受它的原样,但Cython的本地加速功能将不会发挥作用。但是如果你用Cython的特殊语法的类型注释来装饰Python代码,Cython将能够用快速的C等价物来替代缓慢的Python对象。这意味着开发者可以从现有的Python应用程序开始,通过对代码的局部修改来加速它,而不是从头开始重写整个应用程序。

这种方法与软件性能问题的本质相吻合。在大多数程序中,绝大多数的 CPU 密集型代码都集中在几个热点上--这是帕累托原则的一个版本,也被称为 "80/20 "规则。因此,Python 应用程序中的大部分代码不需要进行性能优化,只需要对少数关键部分进行优化。你可以逐步将这些热点翻译成Cython,从而在最重要的地方获得你需要的性能提升。

如何使用Cython

考虑以下代码,取自Cython的文档:

def f(x): return x**2-xdef 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 代码,它很慢,因为 Python 必须在机器的数字类型和它自己的内部对象类型之间来回转换。

现在考虑一下相同代码的 Cython 版本,Cython 的添加部分用下划线表示:

cdef double f(double x): return x**2-xdef 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

如果我们明确声明变量类型,包括函数参数和函数主体中使用的变量(double, int, etc. 我们还可以使用cdef关键字来定义主要用C语言实现的函数,以提高速度,尽管这些函数只能被其他Cython函数调用,而不能被Python脚本调用。(在上面的例子中,只有 integrate_f 可以被另一个 Python 脚本调用。)

注意我们的实际代码没有什么变化。

Cython的优势

除了能够加速你已经写好的代码之外,Cython还赋予了其他几个优势:

与外部C库的工作可以更快

像NumPy这样的Python包将C库包裹在Python接口中,使它们易于工作。然而,通过这些包装在Python和C之间来回走动会使事情变得缓慢。Cython可以让你直接与底层库对话,而不需要Python的介入。(C++ 库也被支持。)

你可以同时使用 C 和 Python 的内存管理

如果你使用 Python 对象,它们的内存管理和垃圾收集与普通 Python 一样。但是如果你想创建和管理你自己的 C 级结构,并使用 malloc/free 来处理它们,你可以这么做。

你可以根据需要选择安全或速度

对于C语言中出现的常见问题,如数组上的越界访问,Cython会通过装饰器和编译器指令(例如,@boundscheck(False))自动进行运行时检查。

如果你确信你在运行时不需要这些检查,你可以禁用它们以获得额外的速度提升,可以在整个模块或只在选定的函数上禁用它们。

Cython 还允许你本机访问 Python 结构,这些结构使用缓冲区协议直接访问存储在内存中的数据(没有中间复制)。Cython的内存视图让你可以高速处理这些结构,并且具有适合任务的安全级别。例如,Python字符串的原始数据可以通过这种方式读取(快速),而不必通过Python运行时(缓慢)。

Cython C代码可以从释放GIL中获益

Python的全局解释器锁,或GIL,在解释器中同步线程,保护对Python对象的访问并管理资源争夺。

如果你有一段没有引用Python对象的代码,并且执行了一个长期运行的操作,你可以用nogil:指令来标记它,允许它在没有GIL的情况下运行。这使得Python解释器可以做其他事情,并且允许Cython代码使用多核(需要额外的工作)。

Cython可以使用Python的类型提示语法

Python有一个类型提示语法,主要由linters和代码检查器使用,而不是CPython解释器。Cython有自己的自定义语法用于代码装饰,但随着最近Cython的修订,你也可以使用Python类型提示语法为Cython提供基本的类型提示。

Cython可以用来掩盖敏感的Python代码

Python模块很容易被反编译和检查,但编译后的二进制文件却不容易。当向终端用户发布Python应用程序时,如果你想保护它的一些模块不被随意窥探,你可以通过用Cython编译它们来实现。

Cython的局限性

请记住,Cython并不是一个魔杖。它不会自动地将每一个Python代码的实例变成快速的C代码。为了充分利用Cython,你必须明智地使用它,并了解它的局限性:

传统Python代码的速度提升不大

当Cython遇到无法完全翻译成C语言的Python代码时,它会把这些代码转化为一系列对Python内部的C语言调用。这相当于把Python的解释器从执行循环中拿出来,默认情况下,这可以使代码的速度适度提高15%到20%。请注意,这是一个最好的情况;在某些情况下,你可能看不到性能的改善,甚至是性能的下降。

本地 Python 数据结构的小幅加速

Python 提供了一系列的数据结构--字符串、列表、图元、字典等等。它们给开发者带来了极大的便利,而且它们有自己的自动内存管理。

Cython 可以让你继续使用所有的 Python 数据结构,尽管速度没有得到很大的提高。这又是因为Cython只是在Python运行时中调用了创建和操作这些对象的C API。因此,Python数据结构的行为与Cython优化的Python代码一般来说很相似。你有时会得到一个提升,但只是一点点。为了获得最佳效果,请使用C语言的变量和结构。

Cython代码在 "纯C "时运行得最快

如果你在C语言中拥有一个标有cdef关键字的函数,它的所有变量和内联函数对其他事物的调用都是纯C的,它的运行速度将与C语言一样快。但如果该函数引用了任何Python原生代码,比如Python数据结构或对Python内部API的调用,该调用将成为性能瓶颈。

幸运的是,Cython提供了一种发现这些瓶颈的方法:一个源代码报告,它可以一目了然地显示Cython应用程序中哪些部分是纯C语言,哪些部分与Python交互。应用程序优化得越好,与Python的交互就越少。

IDG

为一个Cython应用程序生成的源代码报告。白色的区域是纯C语言;黄色的区域显示与Python内部的交互。一个优化良好的Cython程序将尽可能少地出现黄色。最后一行显示了C代码在其对应的Cython代码下的扩展。

Cython NumPy

Cython改善了基于C的第三方数字计算库的使用,如NumPy。因为Cython代码可以编译成C语言,它可以直接与这些库进行交互,并将Python的瓶颈从循环中移除。

但是NumPy,特别是,与Cython配合得很好。Cython对NumPy的特定结构有本地支持,并提供对NumPy数组的快速访问。

然而,如果你想在Cython和NumPy之间建立最紧密的绑定关系,你需要用Cython的自定义语法进一步装饰代码。例如,cimport语句允许Cython代码在编译时看到库中的C级结构,从而实现最快的绑定。

由于NumPy被广泛使用,Cython支持NumPy "开箱即用"。如果你已经安装了NumPy,你只需在你的代码中说明cimport numpy,然后进一步添加装饰以使用暴露的函数。

Cython 剖析和性能

你可以通过剖析代码并亲眼看到瓶颈所在,从而从任何一段代码中获得最佳性能。Cython为Python的cProfile模块提供了钩子,所以你可以使用Python自己的剖析工具,如cProfile,来查看你的Cython代码的性能。

在任何情况下都要记住,Cython并不神奇--明智的现实世界的性能实践仍然适用。

例如,如果你有一个想要在 Cython 中处理的对象集合,不要在 Python 中遍历它并在每一步调用 Cython 函数。将整个集合传递给你的Cython模块并在那里进行迭代。这种技术在管理数据的库中经常使用,所以它是一个很好的模型,可以在你自己的代码中进行模仿。

我们使用 Python 是因为它为程序员提供了便利,并且能够快速开发。有时,程序员的生产力是以性能为代价的。有了Cython,只需一点额外的努力就可以让你获得两个世界的最好结果。

阅读更多关于Python的信息什么是Python?强大的、直观的编程什么是PyPy?更快的Python,没有痛苦什么是Cython?以CC的速度运行的Python教程。如何加快Python的速度如何以聪明的方式安装Python3.8中最好的新特性用Poetry更好地管理Python项目Virtualenv和venv。Python虚拟环境讲解Python virtualenv和venv的注意事项Python线程和子进程讲解如何使用Python调试器如何使用timeit对Python代码进行剖析如何使用cProfile对Python代码进行剖析开始使用Python中的async如何使用Python中的asyncio如何将Python转换为JavaScript(然后再返回)Python 2 EOL。如何在Python的末日中生存下来 212个Pythons满足每个编程需求 24个Python库满足每个Python开发者的需求7个你可能错过的可爱的Python IDE3个主要的Python缺点及其解决方案13个Python网络框架比较4个Python测试框架来粉碎你的bug6个你不想错过的伟大的Python新功能5个掌握机器学习的Python分布8个用于自然语言处理的伟大的Python库


本文标签

热门标签

会员评论