Python 速度慢但开发效率高:为何它仍是首选语言
写在前面
我们来讨论一个经常被提及的问题:Python 的性能。作为 Python 的忠实拥趸,我在各种场景下都积极尝试使用 Python 来解决问题。大家对 Python 最大的抱怨就是它的运行速度慢。有些人甚至因为 Python 的速度不如 C++ 或 Go 而拒绝使用它。本文将阐述,即便 Python 运行速度较慢,为什么它在现代开发中依然值得尝试。
Python 运行时速度虽慢,但在现代开发中,员工时间成本高于硬件成本,因此开发效率更为关键。微服务架构下网络调用成为瓶颈,CPU 周期影响被稀释。高吞吐量系统中,表达性强的语言能减少代码量并提升迭代速度。遇到性能瓶颈时,可通过分析定位关键路径,利用 Cython 或 PyPy 进行局部优化,而非盲目重写。整体而言,Python 在业务逻辑实现上具有显著优势。

我们来讨论一个经常被提及的问题:Python 的性能。作为 Python 的忠实拥趸,我在各种场景下都积极尝试使用 Python 来解决问题。大家对 Python 最大的抱怨就是它的运行速度慢。有些人甚至因为 Python 的速度不如 C++ 或 Go 而拒绝使用它。本文将阐述,即便 Python 运行速度较慢,为什么它在现代开发中依然值得尝试。
优化你最昂贵的资源。
历史上,程序最昂贵的资源是计算机的运行时间。这导致了对计算机科学的研究更专注于不同算法的效率。然而在当下环境中,这已经不再适用。硅的价格已经十分便宜,硬件成本大幅下降。运行时间不再是你最昂贵的资源。一个公司最昂贵的资源现在是其雇佣的员工的时间。也就是开发者自己的时间。对现在的公司来说,完成项目比让项目跑得更快更重要。
你也许会说'我们公司对性能要求很高,我构建的网站应用需要所有的请求在 X 毫秒内返回。'或者'客户认为我们的应用慢而放弃使用我们的应用。'在这里我不是说速度根本不重要,我只是想说明速度不再是最重要的指标,因为它不再是你最昂贵的资源。
![图片]
在编程的世界中当你提到速度,一般是指程序的性能,也就是 CPU 周期。而当你的 CEO 提到速度,他通常指的是业务上的速度,其中最重要的是投入市场的时间(Time to Market)。你的产品或网络应用有多快并不重要,应用采用哪种语言编写的也不重要,甚至是使项目运行投入了多少资金都不重要。最终,唯一能够让你的公司存活下来的是产品投入市场的时间。
这里不是指初创公司观念中的盈利时间,而更多是从想法转换到实际消费者手中所花费的时间。在商业世界中能存活下来的唯一方法是比你的竞争对手更快地进行创新。如果你的竞争对手比你更早地发布产品,那么你有再多的好点子也无济于事。你必须成为市场的第一个进入者,或至少要赶上领先的节奏。一旦你掉队了,那么你就大势已去。
在商业世界中能存活下来的唯一方法是比你竞争对手更快地进行创新。
亚马逊、谷歌、Netflix 等公司深刻理解速度的重要性。它们创建了一个能快速发展和创新的业务系统。微服务就是这个问题的解决方案。本文并不讨论你是不是应该使用微服务,但最起码这些大公司认为它们应采用微服务。
![图片]
微服务天生就很慢。微服务的最基础的概念就是拆分业务边界,并通过网络调用来相互通讯。这也就意味着你需要把一个只占几个 cpu 周期的方法调用转换成网络调用。从性能层面上来说,这简直糟糕透顶。网络调用的速度和 CPU 调用根本不可同日而语。但是那些大公司仍然选择使用微服务。没有比微服务更慢的架构了。
微服务的最大劣势就是其性能,但是它所带来的最大好处是缩短了投入市场需要的时间。通过构建小型项目和少量代码的团队,公司可以以一个非常快的速度进行迭代与演进。这个例子只是为了展示不仅仅是初创公司,大公司也关注投入市场所需的时间。
如果你编写像网络服务器上的网络应用,那么 CPU 时间可能并非你应用的瓶颈。当你的网络服务器处理一个请求,它可能会需要调用多个网络调用,例如数据库或 Redis 缓存。这些服务本身速度很快,然而网络调用的过程却很慢。
一篇博客很好地描述了各个特定操作速度上的差别。如果单个 CPU 周期对应一秒的话,一个从加利福尼亚到纽约的网络调用就大约相当于 4 年。对,网络调用就是这么慢。粗略地估计,在同一数据中心内的一个普通的网络调用需要 3 毫秒,这在前面的对应关系下相当于 3 个月。现在假如你的程序是 CPU 密集型的,需要花费 100,000 个 CPU 周期来处理一次调用。按之前的比例来算,这些时间相当于 1 天。那么如果你用一个慢 5 倍的语言,它也就只花费了 5 天。相对于 3 个月的网络调用,4 天的差别就无足轻重了。
说了这么多我只是想说,即便 python 很慢,但这并不重要。语言的速度(也就是 CPU 时间)几乎不会导致问题。谷歌就这个概念做过一个研究,并写了一篇论文。论文中谈论了设计高吞吐量的系统。在结论中这样描述到:
在一个高吞吐量的环境中使用一个解释型语言看似矛盾,但是我们发现 CPU 时间几乎不是瓶颈因素,表达性强的语言意味着大部分代码是短小的,大多数时间花费在了 I/O 以及原生代码运行时上。
简单说来:CPU 时间几乎不是瓶颈因素。
你可能会说'这观点很好,但是我们确实在 CPU 上遇到了瓶颈,造成了我们网络应用的速度缓慢',或者'在服务器上 X 语言相对 Y 语言需要更少的硬件资源来运行。'这可能都是事实。但网络应用的优势就是你可以几乎无限地进行负载均衡。换而言之,就是使用更多的硬件资源。当然 Python 相较其他语言,如 C 语言,可能需要更多硬件资源。那就使用更多的硬件来解决这个问题。硬件相对于你的人工时间便宜许多。如果你一年内节约了几周的开发时间,这就远胜于你在硬件上所节约下来的花费。
前面我谈论了最重要的是开发所花费的时间。但是问题还是没有得到回答:Python 的开发时间的确比其他语言快么?经过多方调查,我、谷歌以及许多第三方结论都会告诉你 Python 能提升多大产能。Python 抽象化了诸多内容,可以让你专注于你真正的业务逻辑,而不用关心你是应该使用 vector 还是 array 等底层细节问题。
总体来说,争论 python 是否高产,最终讨论的是脚本(或动态语言)与静态类型语言之间的比较。我认为大家都赞同静态类型语言的产量较低,但这里有一篇很好的论文解释了其中的原因。就 Python 而言,曾有研究分析了不同语言编写一个字符串处理程序所花费的时间,并做了很好的总结。
![图片]
使用不同语言编写字符串处理应用所花费的时间。(Prechelt 与 Garret)
在结论中 Python 比 Java 的生产效率高两倍。还有其他诸多研究结果得到类似的结论。Rosetta Code 对不同语言进行了公平而深入地研究。在论文中它们将 Python 和其他脚本 / 解释型语言进行了比较,并认为:
Python 是其中最精练的,甚至比函数式语言更好(平均短 1.2-1.6 倍)。
总体看来 Python 代码的行数总是更少。代码行数听上去是一个糟糕的指标,但是多项研究显示(包括之前提及的两个),在各语言中输入每行代码的时间是不相上下的。因此,减少代码行数也就相当于提高了生产效率。
上述观点的论调听上去像认为优化和速度根本不重要。但是事实是,许多时候运行时效率至关重要。一个例子是,你的网络应用有一个特定的端点需要相当长的时间来响应请求。同时你知道它需要有多快,也知道它要被优化到什么程度。
在这个例子中,发生了下面两件事:
我们不必在应用中对每个服务进行细节调优。每个服务只需要能'足够快'来满足用户的需求就够了。用户会发现某个端点花费了几秒时间返回,但是他们并不会注意到你把一个 35 毫秒的请求优化到了 25 毫秒。你只需要达到'足够好'就可以了。免责声明:不得不说一些应用,如实时拍卖应用,确实需要细节调优,能提升一毫秒算一毫秒。但是这是一个特例,而不是业界的规则。
为了弄清如何优化某个端点,第一步你需要对你的代码进行性能分析,并尝试整理出其中的瓶颈。归根到底:
任何不考虑瓶颈的调优都是幻想。—— Gene Kim
如果你的优化并不解决瓶颈,那你就是在浪费你的时间,而且还不能解决真正地问题。不解决瓶颈,你就不会在性能上得到显著的提升。如果你尝试着在了解瓶颈前优化,你就在和你的代码在玩打地鼠的游戏。在排查和确定瓶颈前优化代码也是'不成熟优化'的表现。Donald Knuth 常被引用下面的观点,虽然他本人称这也是从其他人那儿听来的:
不成熟的优化是万恶之源。
我们应该忘记那些小的性能提升,这占了 97% 的时间:不成熟的优化是万恶之源。但同时我们也不能放过那至关重要的 3%。换句话来说,大部分时间,你不应该关心代码的优化。它们通常已经足够好了。如果没有能达到标准,我们应该只需要改变那 3% 的代码。
我最喜欢 Python 的一点就是它可以让你一步一步地优化你的代码。比如说你有一个 Python 方法,你发现它是你项目中的瓶颈。你已经对其优化了数次,可能是遵循了最佳实践,现在你确定 Python 本身是你应用的瓶颈所在。
在优化之前,必须使用工具定位瓶颈。Python 内置的 cProfile 模块可以提供详细的调用统计信息。配合 pstats 可以生成分析报告,帮助你识别哪些函数消耗了最多的 CPU 时间。
import cProfile
import pstats
def slow_function():
# 模拟耗时操作
sum(i * i for i in range(10000))
if __name__ == '__main__':
profiler = cProfile.Profile()
profiler.enable()
slow_function()
profiler.disable()
stats = pstats.Stats(profiler).sort_stats('cumulative')
stats.print_stats()
Python 是能够直接调用 C 代码的,这就意味着你可以用 C 重写这个方法来减少性能问题。你可以一个一个地进行替换。这个过程能让你调用任何最终编译成 C 兼容指令的优化的代码,也让你能在大部分情况下继续使用 Python,而只在真正需要的时候深入底层进行开发。
有一个叫 Cython 的语言,它是 Python 的超集。几乎是 Python 和 C 的结合体,同时它是渐进的类型化语言。任何 Python 代码都是合法的 Cython 代码,Cython 会将代码编译成 C 代码。有了 Cython,你可以编写模块或方法,渐渐地引入 C 语言的类型和性能。你可以混合使用 C 语言的类型和 Python 的鸭子类型(duck type)。通过 Cython 你可以只在瓶颈处进行调优,而在其他地方仍然使用优美的 Python 语言,两者能完美地结合。
![图片]
使用 Python 编写的太空大规模多人在线游戏 EVE Online 的截图
当你最终遇到了 Python 的性能瓶颈,你不需要将你所有代码移植到其他语言。你总是可以使用 Cython 重写部分方法来满足性能上的需求。这也是游戏 EVE Online 所采用的策略。Eve 是一个大型多人在线电脑游戏,它完全使用 Python 和 Cython 开发。游戏开发人员通过在 C/Cython 中调优瓶颈来达到游戏级的性能要求。如果游戏都能达到性能上的需求,那么大部分情况都应该可以满足。
此外,还有其他方法来优化你的 Python 程序。例如 PyPy 是一个 Python 的运行时编译执行(JIT)的实现,只需要使用 PyPy 切换默认的 CPython,就可以显著地提升你长时间运行应用的运行时性能,如在网络服务器上。对于计算密集型任务,PyPy 往往能提供比 CPython 快数倍的执行速度。
就时间复杂度而言,你可以认为用任何的语言写你的程序的复杂度都是 O(n) 的,其中 n 是代码的行数或指令个数。同一指令的增长速率都是相同的。所以一个语言或运行时的快慢并不重要,就渐进增长而言,所有语言都是等价的。在这个逻辑下,你可以认为,因为某个语言速度快而选择其为开发你应用的语言是不成熟优化的一种体现。你不应该主观地判断某个语言快而不去进行衡量、不去了解将会遇到的瓶颈。
因为某个语言速度快而选择其为开发你应用的语言是不成熟优化的一种体现。
![图片]
综上所述,Python 虽然在绝对运行速度上不及编译型语言,但其极高的开发效率和丰富的生态使其成为现代软件开发的首选。只要合理评估系统瓶颈,利用合适的工具进行局部优化,Python 完全能够满足绝大多数业务场景的需求。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online