生产力的增长是靠牺牲性能换来的。这篇文章不再讨论asyncio(异步IO库)在Python中的运用,而是谈谈最近我一直在思考的一个问题:Python的运行速度。同那些不了解Python的人相比,我属于Python的忠实粉丝,而且我使用Python的频率非常高。目前人们抱怨Python最多的是它的运行速度慢。通常,大部分人拒绝使用Python是因为它比某某语言还慢。尽管如此,我还是建议你使用Python,理由如下:
速度不再重要
以前,运行程序需要花费很长的时间。CPU和内存的售价是比较昂贵的,衡量这两个硬件的一个重要标准是程序运行所花费的时间。电脑和运行所需的电力的价格也非常昂贵。要想优化这些资源,需要基于一条永恒的商业法则:
优化最贵的资源
从历史上来看,最昂贵的资源无疑是计算机运行时间。这就导致了计算机科学注重研究不同算法的效率。然而,随着硅变得便宜,一切都不一样了。运行时间不再是最昂贵的资源。企业目前最为昂贵的资源是雇员的时间。或者换种方式来讲,最重要的是让雇员在规定时间内将程序完成,而不是让程序运行速度更快。事实上,这是很重要的,我再次将它放在这里来引出下面的内容(对于那些仅仅想浏览一下文章的人来说):
程序的开发时间比运行速度更重要。
你可能会说,“我的公司只关心速度,我开发的web程序的所有响应速度都比其他语言快几毫秒”。或者,“我们的客户取消订单是因为觉得我们的程序太慢了”。我并不是想说速度不重要,而是想说,它不再是最重要的问题,同时也不再是最昂贵的资源。
速度!是唯一重要的事情
你认为的编程环境中的速度,其实指的是性能,即CPU周期(机器周期)。而你的BOSS说的编程环境中的速度指的是公司运营的速度,其最重要的标准就是上市时间。最终,无论你的产品还是web应用运行多快,无论它是用什么语言编写的,无论它的运行费用是多少,在一天结束的时候,决定你公司生存与否的是何时上市。我谈的不止是一个创业想法最终盈利需要多长时间,而是整个时间架构——“从想法到最终交付”。企业生存的唯一方法就是比你的竞争对手的创新速度更快。无论你想出多少好的主意,一旦你的竞争对手比你更先“落实”,一切都付之东流。你必须第一个进入市场,至少也要保持领先。一旦你的创新速度变慢了,那么你就可能要完蛋了。
唯一能在商业中存活的方法便是创新速度要快过你的竞争者。
案例:(微服务架构)
像亚马逊、谷歌和Netflix(美国付费视频网站)这样的公司十分清楚快速发展的重要性。他们已经创建了一个业务系统以便更快地发展和创新。微服务架构解决了他们的困难。本文并不打算推荐你使用微服务架构,但是至少帮助你理解为什么亚马逊和谷歌应该使用这一架构。
微服务架构运行一直都比较缓慢,它打破了网络调用的限制。这意味着你将函数调用(一组CPU周期)变成一个网络调用。相信没有什么比这个更糟糕的体验了。同CPU相比,网络调用超级慢。尽管我相信没有哪个架构程序比它更慢了,但这些大公司仍然选择使用微服务架构。微服务架构最大的缺点是性能,最大的优点就是上市时间。通过围绕小项目和代码库建立团队,公司能够更快的迭代与创新。这表明了不仅仅是创业公司,大公司也同样关心产品的上市时间。
CPU不是你的瓶颈
如果你编写一个应用程序,比如web服务器,有可能CPU时间并不是你遇到的最大的困难。当你的web服务器处理一个请求时,它可能生成一组网络调用,比如会用到数据库,或像Redis这样的缓存服务器。尽管这些服务本身响应迅速,对它们网络调用却比较缓慢。有篇不错的博客就指出了某些操作的速度差异。在这篇文章中,作者用人类时间来解释CPU cycle times(机器周期),如果一个单独的CPU周期为一秒的话,从加利福利亚到纽约的一个网络调用则需要花费4年的时间,这太慢了!据粗略估计,假设在同一数据中心内部的普通网络调用需要3毫秒,这相当于我们“人类时间”的3个月。现在,假设你的程序属于CPU密集型,需要10000个周期来响应一个调用,这差不多超过一天了。如果你使用的语言要慢5倍,现在大约就需要5天了。当然,同3个月的网络调用相比,多了4天没多大区别。如果有人为一个数据包需要等待至少3个月以上,那么我不认为多等四天对他们来说有多重要。
最终的结论是:即使python很慢,也影响不大。语言的速度(或CPU时间)几乎从来不是问题。谷歌实际上对这个概念做过一项研究,并写成了一篇研究报告,讨论了高吞吐量系统的设计。
总结来说,在高吞吐量环境中使用解释型语言似乎是荒谬的。但是我们发现,CPU时间基本上不属于限制因素。语言的表达性指大部分程序都比较小,同时将大部分时间都耗费在I/O读写和代码运行上了。此外,灵活的解释型语言无论是在语言层面的实现上,还是在探索很多机器的分布计算方法上,都有所帮助。总之——
CPU时间几乎不是限制因素!
如果CPU时间是个问题呢?
你可能会说,“这样的结论太好了。但是我们的确遇到了CPU的限制,并因此导致我们的web应用程序运行十分缓慢”或“在服务器上使用某某语言会比其他语言需要更少的硬件支持”。上述情况的确可能会发生。但web服务器的好处是,你可以无限地负载均衡。换句话说,可以装更多的硬件。当然,Python可能比C语言等其他语言需要的硬件要多。用硬件来解决CPU的问题吧,硬件比你的时间可便宜多了。如果你在一年的工作时间中能节约出几周时间,那么其价值将超过额外的硬件成本。
所以,Python更快吗?
在这篇文章里,我一直都在讨论开发时间是最重要的。所以还有一个问题:在开发时Python会比其他语言更快吗?有趣的是,我、谷歌和其他一些人都可以证明python的效率更高。它可以为你简化许多事情,帮助你专注于真正想实现的代码,而不是陷入使用向量还是数组这类的琐事。但是你可能不会相信我所说的,所以让我们来看一组数据:
大多数时候,关于python是否更有效率的争论,可以归结为脚本程序(或动态语言)和静态语言之争。我认为大家普遍接受的是静态语言的效率更低。但是有一篇很不错的论文解释了为什么不是这样。特别对Python而言,这里有一篇研究很好地总结了各种语言编写字符串处理程序所需的时间。
不同的编程语言在编写字符串处理程序时所需要的时间
上述研究显示,相同的时间内Python的产出是Java两倍多,还有很多其他的研究也得到了相同的结论,罗塞塔编程(Rosetta Code 一个用不同语言解决相同任务的维基网站)曾经做过关于不同编程语言差异性的深度研究,在研究中,他们对比了Python和其他的脚本/解释性语言,得到的结论是“Python往往是最简洁的语言,甚至匹敌函数式编程语言(平均编写时间比其它语言节省1.2到1.6倍)”
大体上看来,Python中代码的行数通常很短。虽然行数看起来是一个糟糕的度量标准,但是很多的研究,包括之前提到的研究表示,在所有的编程语言中,每行代码的运行时间基本相同。因此,少的代码行数提高了程序的产出率。甚至连coding-horror(Jeff Atwood,一个C#程序员)也写过一篇文章论述Python代码的产出是多么的高。
在我看来,说Python要比其他所有语言都要多产是恰当的,这主要是因为Python有很多内建模块并且有许多第三方库。Python官网上有一篇文章探讨Python和其他的区别(https://www.python.org/doc/essays/comparisons/)如果你不知道为什么Python如此的轻量并且高效,我建议你借此机会去学习一些Python然后自己去探索。这就是你的第一个程序:
import __hello__
如果速度真的很重要怎么办?
听起来,上述观点的论调可能让人觉得优化和运行速度一点也不重要,但是事实是,在很多时候,程序的性能确实非常重要。举一个例子,你有一个web应用程序,有一个终端需要花费很长的时间响应。你知道它需要多快,需要改进多少。
在这个例子中,发生了如下事情:
我们发现了一个终端响应很慢。
我们发现它慢,是因为我们有一个标准去衡量什么才算足够快,但是这个终端没有达到标准。
我们没有必要对应用程序中的所有内容都做微小的优化,只需要让每一部分足够快就可以了。你的用户可能会发现一个终端花费了几秒才应答,但是他们不会发现你把应答时间从35毫秒缩短到了25毫秒。“足够好”就已经是你所需要达到的全部了。免责声明:我应该指出,确实存在一些应用程序,例如实时招投标程序,需要微小的优化,而且每一毫秒都很重要。但是那是个例外,并不能成为准则。
为了明白如何优化终端,第一步你要做的就是纵览整个程序试着找出影响速度的瓶颈在哪里,毕竟,Gene Kim说过,
“不在速度瓶颈处的程序优化都是浮云。”
如果你的优化没处在整个程序最需要的地方,你就是在浪费时间。除非你在瓶颈处做了优化,否则你的程序不会有实质性的速度提升。如果你在找到瓶颈之前就开始着手优化,这就像是在和你的代码玩打地鼠一样毫无作用,这种在你查找并确认瓶颈前进行的优化被叫做过早优化。有一句话批评了这种行为,很多人认为这句话是Donald Knuth说的,但是他自己说是照搬别人的,那就是:“过早优化是万恶之源”
谈到维护代码库,Donald Knuth的原话是:
“在97%的情况下,我们应该忽略小的效率提升,即过早的优化是万恶之源,然而我们不应该错过那关键的3%。”
换句话说,他在说在大部分的情况下,你不应该总想着优化你的代码,它几乎足够好了。在你的代码不够好的时候,我们通常只需要变动3%的代码。让你的终端响应快几纳秒,你不会获得任何的好处,因为你可能只是用if语句代替一个函数。所以在你想清楚哪里是瓶颈后,再去优化代码。
过早的优化包括调用一个更快的方法,甚至使用一个更快的数据结构。计算机科学显示如果两种方法或算法有着相同的渐进增长速度(或者叫大O)那么他们就是相同的,即使在实际使用中速度相差两倍。计算机的计算速度太快了,以至于由数据量增长导致的计算量增大要比它实际的计算速度更重要。换句话说,如果我们有两个大O(log n)即log n级的函数,但是一个比另一个慢两倍,这没什么影响,因为随着数据量的增加,它们会以相同的速率减慢。这就是为什么“过早的优化是万恶之源”,这种优化浪费了我们的时间, 也从未真正地提高运算速度。
从大O的角度考虑,你可以认为所有的编程语言都是大O(n)级的算法,其中n是代码或者指令的行数。一种编程语言或者程序运行时间慢并不是问题,从渐进增长速度的角度考虑,所有的编程语言生而平等。从这个逻辑出发,你可以认为,凭借“速度”因素为应用程序选择一个编程语言完全属于过早的优化,你实际选择的是“据说”很快的编程语言,而没有去测试、没有理解影响速度的瓶颈是什么。
凭借“速度”因素为应用程序选择一个编程语言完全属于过早的优化。
优化Python
我喜欢Python的一点是它可以让你每次优化一点点你的代码。假设你发现一个用Python实现的方法是限制了你代码的速度,并且你可能参照Python速度 或Python性能指南 这样的文档,将代码优化了很多次,你现在已经非常确信Python本身就是运行速度的瓶颈。但Python能够调用C代码,这也意味着你可以用C语言重写这部分代码从而提高性能,你可以一次改写一个方法。这个过程允许你可以使用任何能以C语言的方式编译的语言,写出很多优化瓶颈的程序。这可以让你在大部分时间里都专注于Python,只有在你真正需要的时候再使用较低级的语言。
有一种编程语言叫Cython,是Python的一个超集。它可以被粗略的认为是Python和C语言的融合,是一种渐进式的语言。任何的Python代码都是有效的Cython代码,而且Cython可以编译成C代码。有了Cython,你可以写一个模块或者方法,然后逐渐地形成更多C类型文件和可执行文件,你也可以融合C类型和Python的鸭子风格(动态类型的一种风格)。有了Cython,你可以只在瓶颈处融合优化了的代码,并且在别处保留Python语言的美。
星战前夜的截图:一个用 Python 编写的 space MMO 游戏。
当你终于遇到Python的性能问题时,你不需要把全部的代码都改用其它的语言编写。你只需要在Cython中重写几个方法,就能得到你满意的性能,这就是星战前夜 (EVE Online)所用的策略。星战前夜是一个宏大的多人电脑游戏,使用Python和Cython作为整个架构,通过用C语言和Cython优化代码的瓶颈,实现了游戏级别的性能。如果这种优化方式适用于星战前夜,那么它也能满足大多数人的需求。另外,也有其他的方法可以优化你的Python程序。例如,PyPy是Python的一种JIT(即时编译)实现。对于长时间运行的程序(如web服务器),通过将Cython(Python的默认实现)改为PyPy,可以很好的缩短运行时间。
让我们回顾一些要点:
优化你最昂贵的资源,那就是你自己!不是计算机。
选择一个语言/框架/架构可以使你的快速开发(例如Python)。不要选择那些仅仅是运行很快的技术。
当你的确有性能问题时,找到你程序的瓶颈在哪里。
程序的瓶颈通常不是CPU或者Python语言本身。
如果Python本身成了程序运行的瓶颈(你已经优化了你的代码),那么可以转到热门的Cython或者C语言上。
尽情享受把事情迅速搞定的快乐吧。
原文发布时间为:2017-5-3
本文来自云栖社区合作伙伴“大数据文摘”,了解相关信息可以关注“BigDataDigest”微信公众号