简介:一直以来,Python的性能是大家诟病最多的地方,不少最初采用Python的项目甚至开始迁移到其他语言,Duolingo就是其中一例。而整个Python社区最成功的框架莫过于PyPy,但Python使用大户Dropbox并没有采用,相反,他们另起炉灶写了一个Pyston。关于Python几个老生常谈的话题,Kevin Modzelewski有话要说。
一直以来,Python的性能是大家诟病最多的地方,不少最初采用Python的项目甚至开始迁移到其他语言,Duolingo就是其中一例。而整个Python社区最成功的框架莫过于PyPy,但Python使用大户Dropbox并没有采用,相反,他们另起炉灶写了一个Pyston。关于Python几个老生常谈的话题,Kevin Modzelewski有话要说。
Duolingo (多邻国)是一个完全免费的多国语言学习工具,致力于为全球用户提供一个平等接受语言教育的机会。目前用户数超过1.5亿人(笔者是用户之一),其创始人 Luis von Ahn 是卡内基梅伦大学的教授、图灵测试( CAPTCHAs ,俗称验证码)的发明人、麦克阿瑟家族的一员。
不久前Duolingo工程师Andrekhorie在官方技术博客上 发表了一篇文章 ,阐述了对 Duolingo 这个复杂系统重构的种种心得。在 Duolingo 后端,超过88个课程系统通过机器学习的不断优化后提供给用户使用。 Duolingo 原来的系统是用 Python 编写的,后来用 Scala 重写了一遍。虽然重构系统耗费了大量的人力、物力(工程师们不得不中断新的研发计划转而用几个月的时间来重写整个系统),但这一切都是值得的——重构后的系统时延从750ms降到14ms,uptime则从99.9%提高到了100%。
没有什么例外,所有的公司都会在成长初期欠下这样的 技术债 ——就像你欠银行的钱,随着时间的推移它会像滚雪球一样不断变大。有句话说的好,出来混迟早是要还的。这种痛苦将伴随着企业的成长,直至你不得不面对它。
在系统设计之初,由于经常需要共享数据, Duolingo 的系统架构如下图所示:
这个架构的问题是有太多的严重依赖关系,在这种情况下,越来越多的网络请求使得系统延时越来越大。为了解决这个问题,重新设计架构的主导思想是尽可能解耦所有的依赖,使系统变得简单和健壮(如下图所示)。在新的架构下,经常被共享使用的课程数据离线存储在 AWS S3 上, Duolingo 只需做一些轻量级的缓存即可。
Duolingo 做的最大的一个决定是用 Scala 重写了会话生成器。 Duolingo 后端的会话生成器最初是用 Python 编写的。 Python 的优点就不多说了,但它的缺点也是显而易见的。
作为一门函数式编程语言, Scala 有很多十分吸引人的特性。它十分精炼,编写的代码可读性更强、调试维护也更方便。 Scala 吸取了其前辈编程语言的优点,并解决了那些语言无法克服的痛点,有更好的容错机制,bug也更少。此外, Scala 基于 Java Virtual Machine 意味着有丰富的Java类库可用。在很多复杂度不比 Duolingo 差的大数据项目中都使用了 Scala ,这看起来是个不错的选择。最重要的是在复杂系统编程语言中, Scala 的学习曲线比较容易。
通过这次重构, Duolingo 写了所有的测试用例,一方面提高了系统的稳定性,另一方面积累了许多开发文档。在这个过程中他们无缝集成了许多互相独立的开发组件。
本次重构花费的时间低于 Duolingo 的预期,并且重构后的系统更健壮,代码库也更可读、可维护。在整个重构过程中也遇到了一些痛点,比如 Scala 的一些库缺乏可用的文档;在与Java集成时也遇到了一些麻烦,某些 Scala 的特性不能被很好地支持。
性能数据方面,重构后的系统在最初的几个月里高可用达到100%,而这一数据最初是99%,大部分的延时都发生在从S3的缓存中下载没有被缓存命中的课程数据文件。
鉴于最近大家对 关于Pyston的未来 讨论较多,恰好近日 Pyston发布了0.6.1版 ,最新版的 Pyston 在基准测试中的性能比 CPython 快95%,并使 Dropbox 的性能提升了10%(Dropbox内部有很多项目是用Python编写的, Pyston 是Dropbox发起的一个开源项目,目标是使用LLVM和现代JIT技术开发一款高性能的Python实现)。 Pyston 项目的核心开发者Kevin Modzelewski写了 一篇文章 ,谈了他对 Python 的一些看法。
时光倒流至2013年,当时 Dropbox 网站的Web服务器90%的CPU资源被各种访问请求消耗掉了,服务器采购的速度让所有人都心惊肉跳。当时普遍认为 Python 的瓶颈是I/O,由于这个问题在整个系统中广泛存在,问题相当棘手。放眼望去当时并无成熟的技术解决方案——你不能指望也无法想象几百万行代码的 PyPy 能够与 Dropbox 自身的大量扩展兼容。时至今日Kevin Modzelewski依然坚信,当初的选择是正确的。人们既不想放弃 Python 这个生态,又想提高性能,唯有想方设法改进 Python ,这是不二之选。
另一个常见的抱怨是,当初为什么不采用 PyPy 或 CPython 的代码库?这个问题确实不好回答。兼容性是 Dropbox 首先要考虑的,其次是合理的性能提升。 Dropbox 的需求与 PyPy 在设计理念和技术实现两方面都背道而驰,在Kevin Modzelewski看来,这也是 PyPy 项目继续取得更大成就的瓶颈所在。对 PyPy 的小修小补固然能有所改进,但像内存使用、支持C扩展、性能稳定等问题已经固化到 PyPy 的架构中去,这显然不是修修补补能解决的问题。Kevin Modzelewski对一个为了适用于 Dropbox 而改造过的 PyPy 到底还能不能再称之为 PyPy 表示怀疑。
至于 CPython ,这更多的是一个务实的决定, Dropbox 当时的目标是尽可能多地利用 CPython 。Kevin Modzelewski也承认,时至今日,90%的 Pyston 的代码库都是 CPython 代码。从这一点上来说, Pyston 显然是基于 CPython 的实现。但是,在项目的早期阶段, Dropbox 的首要任务是验证其战略是否正确——利用LLVM开发一款高性能的 Python 实现, CPython 并不适合进行这样的实验。尽管走了一些弯路、重复造了一些轮子,Kevin Modzelewski认为 Dropbox 折腾一番的价值在于他们更好地理解了这些技术,未来也许还会遇到类似的情况,由于前面的经验相信他们能做的更好。
在Kevin Modzelewski看来,没有调查就没有发言权,但是一些人比较热衷于发表不切实际的看法。比如说,V8引擎使得浏览器运行JS的速度变得更快,Kevin Modzelewski在想的是如何使 Python 也能获得这样的速度。Kevin Modzelewski考虑的是,跟动态语言相比,为什么 Python 比较慢呢?而不是说,你看Lua的速度更快,所以用Lua会更好。Kevin Modzelewski希望人们理解, Python 之所以慢是因为其丰富的对象模型,而不是它的动态类型和动态范围。问题是,每一个 Python 操作都有很多关键点,由于很多特性被广泛使用,因此用户就忽略掉了这些地方。
比如,当一个包含局部变量的框架退出后,如何平滑地立即修改对应的函数?在缺少动态语言如JS或Lua那样的模拟机制的前提下,这些特性由于应用广泛所以必须支持。但是人们显然忽略掉了这些。
另一方面, Python 的兼容性也跟大家理解的不一样。例如,Kevin Modzelewski发现 Dropbox 的代码库太庞大以至于兼容性问题不可避免。当从引用计数切换到跟踪垃圾收集器甚至切换到排序字典时,都会引起无数的中断。最终 Dropbox 不得不分离并重新执行这两个实现以匹配 CPython 的行为。
特别是在Web应用程序领域内存使用是大家诟病 Python 最多的地方。一部分是GIL的机制问题——类似于多线程,多进程肯定会占用更多的内存。不管出于怎样的考虑, Python 禁止不同的进程之间共享其内存。这里的内存使用不是大家经常讨论的在Python空间(MicroPython除外)中的内存使用,而且这也是 PyPy 不适用于 Dropbox 的另一个原因。 Dropbox 系统中的许多地方实际上也是有这种限制,其中一个关键指标是“每GB内存的每秒请求次数”。Kevin Modzelewski一度认为,当请求次数以50%的速度递增,而内存以2倍的速度扩展是可行的,实际上这在内存绑定类服务中根本不可行。
Kevin Modzelewski最后表示,尽管有上述问题,但这在当时都是经过考量的选择,如果再来一次他相信能做的更好。亲爱的读者,你对Python的认识是否因此得以刷新了呢?