何时使用 C/C++编写扩展是一个合理的决定,这很难说。一般的经验法则是, 从来没
有,除非你没有别的选择 。但是,这是一个非常主观的说法,这留下了很多空间来解释什
么在 Python 中是不可行的。事实上,很难找到一个使用纯 Python 代码无法完成的事情,
但是对于有些问题,扩展可能特别有用。
● 在 Python 线程模型中绕过全局解释器锁(Global Interpreter Lock, GIL)。
● 提高关键代码段的性能。
● 集成第三方动态库。
● 集成以不同语言编写的源代码。
● 创建自定义数据类型。
提高关键代码段的性能
说实话,由于性能问题,很多开发人员不选择使用 Python。它不会快速执行,但可以
让你快速开发。尽管如此,无论我们作为程序员的水平如何,由于这门语言,我们有时可
能会发现一个使用纯 Python 无法有效解决的问题。
在大多数情况下,解决性能问题只是取决于选择正确的算法和数据结构,而不是编程
语言天花板的限制因素。如果代码编写的有问题或者没有使用合适的算法,为了缩减一些
CPU 的周期而去依赖一个扩展,实际上,这不是一个好的解决方案。通常可以将性能提高
到可接受的水平,而不需要通过将另一种语言引入到当前技术栈中来增加项目的复杂性。
如果可能,应该首先这样做。无论如何,即使使用最先进的算法和最适合我们处理的数据
结构,我们也不能仅仅使用 Python 去适应一些任意的技术性约束。
对应用程序性能提出一些明确限制的示例领域是实时竞价(Real Time Bidding,RTB)业
务。简而言之,整个实时竞价的过程就是以类似真实拍卖或证券交易的方式购买和销售广告库
存(广告位置)。交易通常通过一些广告交易服务进行,这些服务将可用的库存信息发送到有
意购买的需求方平台(Demand-Side Platforms,DSP)。这是一个令人兴奋的地方。大多数广告
交易平台使用OpenRTB协议(基于HTTP)与潜在投标人进行通信,其中DSP是负责为其HTTP
请求提供响应的站点。并且,广告交易对整个过程(从接收到的第一个 TCP 数据包到服务器
写入最后一个字节)有着非常严格的时间限制(通常在 50 到 100ms 之间)。为了更好的处理业
务,DSP 平台每秒处理数万个请求并不少见。在这个业务领域,能够缩短请求处理的时间,哪
怕是几毫秒,也是生死攸关的事情。这意味着,在这种情况下,把代码移植到 C 可能是合理的,
但是只有当它成为性能瓶颈并且不能在算法上进一步改进。正如有人曾说过:
“你不能打败一个用 C 写的循环。”
集成现有的使用不同语言编写的代码
在计算机科学发展的短暂历史中,很多有用的库已经被编写了出来。每当一种新的编
程语言出现时,忘记所有的遗产将是一个巨大的损失,并且,使用新语言可靠地移植任何
软件都是比较困难的。
C 和 C++语言似乎是最重要的语言,它们提供了许多库和实现,你可以直接把它们集
成到你的的应用程序代码中,而不需要将它们完全移植到 Python。幸运的是,CPython 已
经用 C 语言编写,所以集成这样的代码的最自然的方式是通过自定义扩展。
集成第三方动态库
集成使用不同技术编写的代码不会随着 C/C++结束。许多库,特别是那些封闭源的第三方软件,以编译二进制文件的格式被分发。在 C 中,加载这样的共享/动态库并调用它们的
函数是很容易的。这意味着你可以使用任何 C 库,只要使用 Python/C API 通过扩展包装它。
这当然不是唯一的解决方案,还有一些工具,如 ctypes 或 CFFI,这些工具允许你使用
纯 Python 与动态库交互,而不需要在 C 中编写扩展。很多时候,Python/C API 可能仍然是一
个更好的选择,因为它提供了一个更好的集成层(用 C 编写),可以分离其余的应用程序。
创建自定义数据类型
Python 提供了多种多样的可选的内置数据类型。其中一些使用了最先进的内部实现(至
少在 CPython 中),这些实现针对 Python 语言进行了专门的优化。大量开箱即用的基本类
型和集合类型可能令新手感到印象深刻,但很显然,它们无法涵盖我们所有可能的需求。
当然,你可以在 Python 中创建许多自定义数据结构,完全基于某些内置类型构建它们,
或者从头开始构建一个全新的类。不幸的是,对于一些严重依赖这种自定义数据结构的应
用,性能可能会不够。复杂集合(如 dict 或 set)的整体功能依赖它们的底层 C 实现。为什
么不这样做,在 C 中实现一些你自己的自定义数据结构呢?
编写扩展
如前所述,编写扩展不是一个简单的任务,你需要付出很多努力的工作,同时,它也
可以给你带来很多的优势。使用诸如 Cython 或 Pyrex 之类的工具,或者使用 ctypes 或
cffi 简单地与现有的动态库集成,通过这些方法开发自己的扩展是最简单的,也是值得
推荐的方法。这些项目会极大的提高你的开发效率,并且降低代码的开发难度,使代码具
有更好的可读性与可维护性。
总之,如果你是新了解这个话题,现在你就可以开启自己的冒险之旅,只是使用少量
的 C 和 Python/C API 就可以开发一个自己的扩展。这将提高你对扩展的工作原理的理解,
也将帮助你了解替代解决方案的优势。为了简单起见,我们将以简单的算法问题为例,并
尝试使用 3 种不同的方法来实现:
● 编写纯 C 扩展。
● 使用 Cython。
● 使用 Pyrex。
我们的问题是找到斐波那契数列的第 n 项。正常情况下,你不太可能会通过编写一个编译
的扩展来解决这种问题,它非常简单,所以这是一个非常好的例子,用来说明把任何 C 函数关
联到 Python/C API。我们唯一的目标是清晰和简单,所以我们不会尝试提供最有效的解决方案。
一旦我们清楚这一点,我们在 Python 中实现的 Fibonacci 函数的参考实现如下:
“”“提供了斐波那契数列函数的 Python 模块”“”
def fibonacci(n):
“”“递归计算返回斐波那契数列的第 n 项.
“””
if n < 2:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
注意,这是 fibonnaci()函数的最简单的实现之一,还可以做很多的改进。我们拒
绝改进我们的实现(例如使用备忘录模式),因为这不是我们的例子的目的。同样,当讨论
C 或 Cython 中的实现时,即使编译的代码提供了更多的改进的可能性,我们也不会优化我
们的代码。
Python 高手编程系列三百六十一:为什么你想用扩展
最新推荐文章于 2024-11-06 19:12:31 发布