我是视频的作者,首先感谢朋友们的关注,并且诞生了这个论题,让我可以看到大家对我视频中的观点的反馈。对于大家的讨论,在日常时间允许的条件下,我会尽量参与。下面就我目前已经看到的朋友们的评论,做一下展开。


一、如果说 Python 协程是个优势,那其他语言难道就没有协程吗? ( @潘俊勇 )

对其他语言来说“协程”或许是近年来才流行起来的一种“新技术”,但是对 Python 来说,协程早在 1998 年就已经出现并且大规模使用至今 —— 虽然从表面上看,各种语言的协程实现都大同小异,比如 Goroutine 和 gevent.patch_all() 都可以直接把整个语言协程化,使用起来非常方便 —— 似乎“只要增加了协程这个新特性就可以在任意语言中直接使用了”,但实际上协程具有极深的技术栈。Python 在之前的 20 多年里,在积累上的领先,让 Python 和其他语言有了本质上的区别 —— “可以大规模商用”和“玩具”的区别。下面我来简单摘取几个方面,来说明在这之前的 20 多年里面,Python 可以在协程技术上比“后起之秀们”多做多少事情。

1、调度器

和其他语言不同,Python 在长时间的积累中形成了大量可用的协程调度器,包括“抢占式”、“非抢占式”、“全功能”、“高性能”等各种实现,以适用于各种应用场景的需求。我使用过的调度器就有 Stackless Python、Gevent、Curio 以及 Eurasia ① 等 —— 在这里,Eurasia 是我于 2007 年开源的协程服务器框架,使用了我自己开发的轻量级协程调度器,在性能上具有相当大的优势。而在我开源的另一种协程服务器框架 Slowdown ② 中,我则是使用了在功能特性上更为全面的 Gevent 作为调度器,更加适合用于搭建复杂应用。在不同的应用场景下能够拥有如此之多的选择,是 Python 在协程技术上,成熟性的体现。我开发的 Eurasia 发布于 2007 年,并在接下来的时间里一直在投入使用 —— 直到 6 年以后 Goroutine 才开始出现 —— 而此时,距离 Python 著名的大规模协程应用 EVE online 的推出,更已经经过 10 年。

2、专业性

协程在“栈切换”“调用上下文”上,和一般程序具有很大的差别,特别是和线程池、多进程配合使用时,情况会更加复杂。如果没有技术积累,开发者往往不知道“代码在何种情况下会出现意外情况”,在出现错误时也不知道该如何正确地去调试 —— 这让协程技术虽然门槛很低,入门容易,但是在实际使用中,它会变成一个巨型的“天坑”—— 而 Python 长期在协程上的实践,形成了大量的技术积累,和无数的专家用户。这让你在遇到困难时,更容易查阅到解决方案,并获得更多的帮助 —— 而不是让你在协程技术领域中拓荒、踩坑。

3、文件/数据库

要在协程架构中真正实现高并发,就必须要把系统中的所有操作全部进行异步化 —— 任何没有异步化的节点全部都会变成性能瓶颈 —— 这相当于是要把整个操作系统都进行异步化改造,如果没有像 Python 这样,经过了漫长的积累,是无法做到的。其中“文件”和“数据库”就是很难异步化的一个例子 —— 像 Python 中著名的 Tornado 就是只实现了网络部分的“协程异步化”,却对文件 IO 无能为力的一个典型案例。这样就只能被迫使用“基于多线程的应用层框架”,以“传统的多线程模式”来处理文件和数据库的请求。因此 Tornado 在事实上,仍然只是一个传统的,受限于“文件 IO 瓶颈”以及“线程数量”的服务器框架,同时数据库服务器也会变成整个系统的瓶颈。其他像 Goroutine 协程,在遇到文件及数据库时,也同样会退化为线程池,这也是其他各种语言的常态 —— 其实在 Python 世界中,以我自己为例,在使用协程的十几二十年的时间里,早已完成“缓存式文件 IO”及“数据库底层磁盘 IO”的“协程异步化”改造。这意味着高级的 Python 协程系统,不仅可以同时响应“百万级的网络 IO”,也能处理同等并发规模的“文件读写”和“SQL 请求”。这使得 Python 的协程可以贯穿整个系统,没有任何一个节点成为瓶颈。


二、说 Python 的运行速度快是不是太扯了? ( @季晨曦 @游汉 )

视频中的观点是“程序性能只和瓶颈有关”,“语言快不快只和它拥有多少绕过瓶颈的手段有关”。开发效率越高,语言可以实现的性能优化就会越多,程序也就越快 —— 表面上可以用其他语言来改写 Python 程序来获得更高的执行效率,但是可能会花上好几倍的时间 —— 而在同样这段时间里 Python 已经可以写出更快的实现了。

在 PyPy 出现以前,Python 主要的 JIT 还是 Psyco 的时候,开发者就已经需要在 Unix/Win32 等不同平台下来维护 Psyco 的 32 位版本了,但随着 64 位 CPU 的逐渐普及,要想同时维护多个 OS,以及 MIPS/ARM/x86 等 CPU 架构,并同时维护 32 位和 64 位 CPU 下的版本,这让维护 Psyco 本身就变得非常吃力,在 JIT 算法上进行突破就变得更加不可能了。于是 64 位 CPU 的推出,成了压跨项目的最后一根稻草,Psyco 的开发者在一篇文章中最后提到 ③ ,他发现只有依赖 Python 的开发效率才能让 JIT 更进一步,尤其是在开发阶段 Python 可以直接解释执行的特点让 JIT 的调试工作变得非常容易。所以 Pysco 的开发者停止了 Psyco 项目,并转而开创了 PyPy 项目 —— 这说明当一个项目的“复杂性”和“工作量”到达一定级别时,Python 已经不是仅仅能够“快过”C 语言这么简单了,事情会变成“如果不使用 Python,问题可能根本就没有办法解决”的程度。故而在视频里“JIT 开发”被当作典型案例来使用 —— 当然这肯定不是孤例。

在许多时候,我们可能只是把 Python 当作“能跑起来的伪代码”用来进行“快速原型开发”,一旦原型验证完成,程序员会有一个本能的冲动,就是把它修改成“正式”的 C 语言版本来“一劳永逸”、“以利千秋”。IBus 输入法便是从一个 Python 项目逐渐 C++ 化,并让性能得到提升的一个很好的例子,但是更多的 Python 项目却并没有像意料中那样,最终走上 C 语言化的这条道路,而是依旧坚持使用 Python 语言来进行开发。所以在“从‘Python 快速原型开发’到‘C 语言最终正式版’”的这个演进剧本里一定是出现了某种变数 —— 当开发者准备替换 Python 时,他们发现:

  1. 如果使用其他语言来强行改写 Python 程序,由于瓶颈可能并不在语言这个层面,可能根本就无法在性能上得到可观的提升。
  2. 如果不使用 Python,开发和维护的成本可能会大幅升高到无法承受的程度,强行替换 Python 只会让程序在“功能”和“性能”上全面落后于 Python 版本的升级速度。

这指向一点:只要存在大量的 Python 项目,在经过无数次升级后,仍然没有被 C 语言化,那么在这些项目中,Python 的执行性能肯定不会输于其他语言。“这些项目其实‘都’是 Python 程序运行速度快的例子”。

在后面我即将发布的该系列视频的第二集「PYTHON 为什么是最快的语言(时空篇)」中,我会提到,由于 Python 语言简洁的特性,会让程序看上去都非常简单,这掩盖了每个 Python 程序事实上的复杂度,并给人以一个错觉,以为用其他语言“也”可以轻松地写出具有同等复杂度的程序。而等到真正用其他语言来着手实施的时候,才发现要实现同等复杂度的程序,语言性能下降的幅度,最终会出乎所有人的意料。


三、Python 在执行效率上会不会被编译型语言吊打? ( @山海师 )

一个例子是 Java 的 WEB 服务常常会给人以性能不行的印象,然而事实却是,Java 是一种逐行执行性能很高的语言,Java 的 socket 服务器其实也拥有相当不错的性能。但是当把应用逻辑加进来以后,为了漂亮地解决各种业务逻辑,服务架构变得异常复杂和庞大,以至于曾经出现过著名的“厚胶合层怪物”:“J2EE”,并最终把 Java 服务的性能从高空拽下地板。这说明在应用领域,“逐行执行性能”并不是“最终真实性能”的决定性因素。只有抑制住让架构无限膨胀的冲动,才能在性能上最终胜出。PHP 这种“最好的编程语言”就曾长期被人批评为“不够面向对象”、“缺少企业级开发能力”—— 但无论如何,在客观上,这使得 PHP 很难写出痴肿的应用架构,这导致 PHP 编写的 WEB 服务,最终无论是在“性能”上还是在“功能”上都相当出色。

感谢 Python 出色的开发效率,重新制造一个轮子变成了一件似乎没有什么成本的事情,所以在 Python 中诞生了无数的 WEB 框架,这是一个只有在 Python 世界才会出现的独特现象 —— 其中就包括我开源的“Slowdown”(及前面提到的“Eurasia” 等)项目。这是一个从 socket 底层到应用层,都完全使用 Python 开发的服务器框架。那么问题来了,这里使用 Python 来进行应用层开发是可以理解的,但是为什么连 socket 底层都要使用 Python 来开发,这不会有性能问题吗?虽然有点违反直觉,但我的答案是:“使用 Python 进行底层开发只会让服务器性能变得更好”。

首先,使用 C 语言来处理 socket 通信和 IO 调度,的确会让服务器的底层性能有所提升。但是我们无可避免地要将底层接口“封装”给 Python 等其他语言的“应用层框架”使用,这就涉及到了“底层”到“应用层”的接口转换,在转换过程中,相当一部分“底层接口”的“灵活性”将无可避免地牺牲掉了。

  1. 为了弥补这部分灵活性的损失,“底层”和“应用层”需要被迫调整出更加复杂庞大的封装架构,增加更多的接口,来应付一些涉及到“底层”的需求。
  2. 如果底层和应用层实现了彻底的封装隔离,许多通过操作底层可以直接轻松处理掉的需求将无法实现,或者只能通过其他相对复杂的方法来“近似”实现。

如果你是某个应用框架的用户,看到这里或许会有一种似曾相识的感觉。因为曾经有个功能你发现框架并没有提供,而要把这个功能加上,则需要操作框架底层的权利,但是由于底层已经被“封装”得“很好”是无法接触的 —— 这最终让你动弹不得 —— 而当问题被提交给框架的维护者时,解决方法往往是“为每个新增的问题设计出更多的接口”、“重构出更好的架构”。这样,框架的复杂度就会逐渐滑入失控,变得臃肿,最终让性能大幅下降。

而使用 Python 来统一开发“应用层框架”和“服务器底层”,则可以避免让“底层”和“应用层”进行跨语言接口转换与封装 —— 进一步,可以让“底层直接穿透到顶层”,并最终让“底层和应用层完全合并”,胶合层消失,框架(framework)程序库(library)化。最终实现架构简化,和性能反超。

而这,对 Slowdown 之类的协程服务器具有着更加非凡的意义。由于协程是用之不竭的,所以可以“利用协程来维护‘无数’长连接的上下文”来“等待长时间执行结果”而无需受到“传统架构下线程数量的限制”,不需要通过“中断连接上下文”来“释放有限的线程”用额外的缓存架构和消息队列来传递“长时间的执行结果”。这样就可以把大量的因“受限于传统线程架构”而增加的中间架构(中间服务器、IPC)给精简掉了。而此时,由于应用层中“长时间任务的上下文”和底层的“长连接上下文”是紧密耦合的,所以在这里“底层穿透性”就尤为重要了。

结合上述内容,我们不难发现使用 Python 将底层与应用层进行融合,最终可以起到将整个系统的架构大幅简化的惊人效果。这样匪夷所思的结果出现了:使用 Python 进行服务器底层开发,在整个系统层面反而取得了更好的性能。目前有大量的基于 Gevent 或者 Curio 等框架的 Python 项目,仗着自己是协程架构,性能起点高,不管有意无意,通常会采用从网络底层到应用层的全栈式设计,虽然没有用到编译型语言来“优化”底层,却在 benchmark 中都具有不错的成绩,这又成为 Python “直接用于性能敏感的底层开发”,并“在性能上超过包括编译型语言在内的其他语言”的典型案例。


四、和其他解释性语言的性能比较 ( @季晨曦 )

下面我们来谈一下视频中提到的“开发效率 == 运行效率”这一条,是否同样可以套用在其他语言上的这个问题。简单来说,答案是肯定的 —— 由于在“公平”“同等”的时间里,开发效率高的编程语言一定可以实现更多的“优化”绕过更多的“瓶颈”,任何语言只要能够拥有极致的开发效率,那么(在不是强烈依赖于逐行性能的多数场景下)一定会拥有不错的运行效率。这样在许多时候,程序“运行效率”的比拼,其实就是“开发效率”的比拼。那么其他语言是否拥有和 Python 一样的开发效率呢?这就是一个非常依赖于主观判断的问题了。所以我试图采用一个“更为客观”的标准来对编程语言的开发效率进行评价:“有效项目数”—— 如果一种语言的开发效率很高,那么在“足够长的时间段”和“相同的时间”里,它一定会开发出比别人更多的项目,“产量更高”。在翻阅了 Github 的“Ranking”、“Trending”、“Stars”中的项目之后,我发现这些指标几乎都被两大语言所“霸榜”:“Javascript”和“Python”。这意味着“单从客观指标上来讲”,大部分语言的开发效率尚不能达到 Python 的级别。依赖于开发效率,Python 在单位时间里就可以生产出更多的优化算法,绕过更多瓶颈,从而实现更高的执行效率。这样 PYTHON 作为最快的编程语言,可以说实至名归。

—— 2021年 1月 6日 17:27


① Slowdown 基于协程异步的服务器及开发框架 github.com/wilhelmshen/

② Eurasia 基于协程异步的服务器及开发框架 code.google.com/archive

③ 由于时代过于久远,该文章的归档暂时没有找到。可以参阅 Psyco 项目主页上“Related project”一节 psyco.sourceforge.net/l