为何移动Web应用程序很慢(译)

前些日子,看到Herb Sutter在本身的博客中推荐了一篇文章《Why mobile web apps are slow》,在推荐里他这样写道:php


“I don’t often link to other articles, but this one is worth reading.”html

  我不常常连接到其它文章,可是这篇文章的确值得一读。前端

“He offers data (imagine!) to justly debunk many common memes and “easy answers” that routinely litter HN/Reddit/Slashdot comment threads.”java

  这句话挺难翻译的,大概意思应该是做者使用了确切的数据来支持本身的观点,而不像其余不少人同样只是随意地发出毫无根据的评论。android

“Don’t be distracted by the author’s viewpoint and emphasis on “iOS and Javascript” development – the article covers lots of important ground, including:ios

  • developing for ARM vs. x86;
  • developing for desktop vs. mobile;
  • managed vs. native code performance;
  • JIT issues vs. inherent language design tensions;
  • why garbage collection is not at all the panacea it’s often billed to be and often needs to be emphatically avoided (did you realize Apple already jettisoned GC?); and
  • as many of you know already, why if you’re serious about performance you’ll be seriously serious about memory usage and access patterns as a first-order issue.”

  不要被做者的观点以及iOS和Javascript开发等字眼分散注意力——这篇文章包含了不少重要的基础知识,包括:git

  • ARM平台编程和x86平台编程比较;
  • 桌面环境编程和移动设备编程比较;
  • 托管代码和原生代码性能比较;
  • JIT相关话题和语言内在的设计张力;
  • 为何垃圾回收不是宣传中所说的万能药,并且常常被强调要避免使用(你意识到苹果公司已经抛弃了GC吗?);
  • 就像大家中的不少人已经知道的那样,为何若是你很在意性能,那么你就应该认真严肃地将内存使用和访问模式做为最优先须要考虑的问题。

既然Sutter大神如此推荐,我就好好把这篇文章看下来了,的确收获颇丰,因此特地把这篇文章翻译下来,一方面加深理解,另外一方面跟你们分享。我翻译的首要目标是可读性和流畅性,并不必定拘泥于字眼;难以翻译和习惯用英文表达的词汇会保留。我能够保证理解做者95%以上的意思(毕竟是技术类文章),可是做者的一些幽默我极可能无法传神地翻译出来,还请你们包涵。程序员

(提示:这是一篇很是长的文章,认真读下来可能须要一段时间。下面是正文翻译。)github


我写过很多文章来讨论为何移动Web应用程序很慢,这也引发了很多的讨论。可是不幸的是,这些讨论没有像我喜欢的那样的基于事实web

因此我这篇文章的目地就是给这些问题带来一些真正的证据,而不是仅仅过来对骂。在这篇文章的中,你能够看到基准测试(benchmark),能够看到专家的观点,你甚至能够看到很是诚实(honest-to-God)的期刊文章。这篇文章有超过100个引用(不是开玩笑)。我不保证这篇文章能使你信服,甚至不保证这篇文章中的全部内容都是正确的(在这样大规模的文章中作到这一点几乎是不可能的),可是我能够保证这是一篇关于许多iOS开发者都抱有的想法——移动Web应用很慢而且会在可预计的将来继续如此——分析最完备和全面的文章。

如今我要警告你:这是一篇长得吓人的文章,差很少10000字。固然,这是我故意的。我更喜欢好文章,而不是流行的文章。我尝试使得这篇文章成为前者,同时宣扬我认同的风气:咱们应该鼓励那些优秀的、基于证据的、有趣的讨论,不鼓励那些诙谐、哗众取宠的评论。

我写这篇的文章,在某种程度上是由于这是话题已经到了一种争论不休的地步。这不是另外一篇争论的文章,若是你想看到30秒左右的对骂:“真的!Web应用很渣!”和“谁说的?Web程序挺好!”,那么这篇文章不适合你。另外一方面,据我所知,到如今为止尚未一个关于这个话题全面的、正式的、理性的讨论。这篇文章中我尝试去理性地讨论这个激起千层浪的话题,尽管这多是一个很是愚蠢的想法。这里我给本身辩护一下,我相信这个问题与那些原本能够更好地去讨论却没有这样作的人更有关系,而不是主题自己。

若是你想知道你那些原生代码(native code)程序员朋友为何在现在开放的网络革命时期还在写着万恶的原生代码,那就把本页面加入书签吧,给本身倒杯咖啡,找出一个下午的时间,找到一个舒服的椅子,而后咱们就正式开始吧!

简单回顾

上一篇博客中写道:基于SunSpider的benchmark给出的数据能够看出当今的移动Web应用很慢。

若是你认为“Web应用程序”就是“一个网页加上一两个按钮”,那么你就可让那些花哨的benchmark——好比SunSpider——滚一边去。可是若是你认为“Web应用程序”是指“简单的文字处理,简单的照片编辑,本地存储和屏幕之间的切换动画”,那么除非你有想死的心,不然你永远不会愿意在ARM上写Web应用程序。

你应该先读一下那篇文章,可是我仍是在这边给你看下benchmark:

关于这个benchmark,主要三种主要的批评:

1. JS比原生代码要慢并非什么新鲜事了,每一个人在上第一学期的计算机基础课时讨论编译型语言、JIT语言和解释型语言是时候就知道了。问题是JS是否是慢到已经成为你如今所写软件的大问题了,可是像这样的benchmark并不能说明这个问题。

2. JS是很慢,这也的确是个问题,可是它在变得愈来愈快,因此在不久的将来,咱们能够发现它不会那么慢了。因此你们一块儿学JS吧。

3. 我是Python/PHP/Ruby的服务器端的开发者,我不知道大家在说什么。我知道个人服务器比大家的移动设备快,可是若是我能够自信地保证使用真正的解释型语言写出支持上千个用户的代码,大家难道不能用一个带有高性能JIT的语言写出一个支持单个用户的代码吗?真的有那么难吗?

我有一个至关高的目标,那就是反驳以上全部观点:是的,JS的确是慢到必定程度了;不,它在不久的将来不会变得有多快;不,你在服务器端的编程经验不能正确地映射到移动应用中。

可是真正的问题在于,在全部讨论这个话题的文章里面,基本上没有人真正量化JS到底有多慢,或者提供某种真正有用的比较标准(相对于什么来讲慢)。为了纠正这样的状况,我在这篇文章中提出了三种(不只仅是一种)比较JavaScript性能的办法。我不会说“JS在什么状况下都慢”,而是真正量化它慢的程度,而且将它跟咱们再日常编程经验中的事情作对比,这样你就能够根据这个结果结合本身的编程平台作出决定,你也能够本身计算下看看是否JavaScript适合你本身的特定问题。

OK,可是JS的性能相比于原生代码到底如何?

这是一个好问题。为了回答这个问题,我从Benchmark Game中随意抓取了一个基准测试。而后我找到了一个作一样benchmark的较老的C程序(老到不像不少新程序有一些x86特性)。我在本身的iPhone 4S上分别测试Nitro和LLVM。全部的代码已经传到了Github

这是一个随机的测试,正如平常生活中运行的代码同样。若是你想要一个更好的实验,能够本身运行。我运行这个实验还有另一个缘由,就是由于其它的实验都不存在LLVM和Nitro的对比。

在这个综合的基准测试中,LLVM一致地比Nitro快4.5倍:

Screen Shot 2013-05-14 at 5.32.06 AM

若是你在想“若是是计算密集型(CPU-bound)的功能,本地代码比Nitro JS快多少呢”,那么答案是差很少5倍。这个结果大体上和Benchmark Game在x86/GCC/V8上面的结果一致,那里面的GCC/x86一般比V8/x86快2到9倍。因此结果大体上是正确的,不管是ARM仍是x86。

可是1/5的性能对每一个人来讲还不够好吗?

在x86上是足够好了。当渲染一个电子表格的时候,CPU的计算能有多密集呢?其实并非那么难。问题是,ARM不是x86。

根据GeekBench的结果,最新的MacBook Pro的性能是最新的iPhone性能的10倍。这其实不算太大问题——电子表格没那么复杂。咱们能够忍受10%的性能。可是我还要把它除以5?好家伙!咱们如今只有桌面性能的2%了。

OK,可是文字处理到底有多难?咱们可不能够用一个m68k芯片加上一个协处理器来搞定呢?这是一个能够回答的问题。你可能记不起来,Google Doc的实时协做以前事实上还不是一个正式的功能,后来他们进行了大规模的重写而且在2010年4月份加入到Google Doc里面。咱们来看一下2010年浏览器的性能:

BrowserCompChart1 9-6-10[7]

从图中能够清晰地看到,iPhone 4S在Google Docs的实时协做方面彻底不是桌面网页浏览器的对手。固然了,它仍是能够跟IE8比上一比的。恭喜iPhone 4S,可喜可贺。

咱们再看看另一个正经的JavaScript应用:Google Wave。Wave历来没有支持IE8,由于它实在是太慢了。

Notice how all these browsers bench faster than the iPhone 4S?

看到这些浏览器比iPhone 4S快多少了吗?

注意,全部支持的浏览器的得分都低于1000,其中那个得分3800的由于太慢了而被忽略了。iPhone得分为2400。差很少和IE8同样,太慢几乎没法运行。

这边要说明的是,在移动设备上实现实时协做是可能的,只是不太可能用JavaScript来实现。原生代码和Web应用的性能差距基本上和Firefox与IE8的性能差距差很少,这么大的差距足以影响正常的工做。

可是我感受V8或者是现代JS已经有了接近C的性能了?

这取决于你怎么理解“接近”了。若是你的C程序运行了10ms,那么一个运行50ms的JavaScript程序差很少是接近C的速度了。若是你的C程序运行了10s,那么一个运行了50s的JavaScript程序对于大多数正常人来讲极可能就不是接近C的速度了。

硬件角度

1/5的速度在x86上是没问题的,毕竟x86起点就比ARM快10倍,你还有不少上升空间。解决方案显然是让ARM变成10倍快,这样就能够跟x86竞争了,而后咱们就能够不用作任何工做就能够获得桌面环境的JS性能了。

这个方法行不行得通取决于你是否相信摩尔定律,以及给每一个芯片配置一个3盎司的电池是否可行。我不是一个硬件工程师,可是我曾经为一家大型半导体公司工做过,那里的人告诉我说当今硬件的性能基本上是制做工艺(process)起的做用。iPhone 5使人印象深入的性能主要是由于其芯片工艺从45nm作到了32nm,减小了差很少1/3。可是若是想继续这么作,苹果就要达到22nm的工艺。

顺便提一下,Intel22nm工艺的Atom处理器如今还没上市。并且Intel不得不从新发明全新的半导体,毕竟原来的半导体在22nm级别已经不适用了。他们会把工艺受权给ARM?再好好想一想吧。现在22nm的产品少之又少,并且大部分被Intel掌控着。

事实上,ARM彷佛已经在着手在明年尝试28nm了(看看A7),同时Intel正在尝试22nm甚至在稍微晚些时候尝试20nm。从纯硬件的角度,我感受具备x86级别性能的x86芯片极可能远远比具备x86性能的ARM芯片更早登陆智能手机市场。

看一个前Intel工程师给我发的邮件:

我是一个前Intel工程师,刚开始从事于移动微处理器的工做,后来工做转向了Atom处理器。不管如何,我有一个很偏激的观点,即x86从较大的核心转向手机市场的难度远比ARM从头开始设计技术细节以达到x86的性能级别的难度要低不少。

再看一个机器人领域的工程师给我发的邮件:

你说得很是对,这些(译注:指的是ARM的发展)不会带来多大的性能提高,Intel可能在近几年以内就会有更高性能的移动处理器。事实上,移动处理器当前和桌面处理器面临着一样的问题,即工做频率达到3GHz左右的时候,再提升时钟速度就不可避免地使得功耗大大增长。这种状况一样会发生在下一代工艺上,尽管IPC(Instruction per Clock,即CPU每一时钟周期内所执行的指令多少)会获得一些提升(差很少10%-20%)。在面临这种限制的状况下,桌面处理器开始向双核和四核方向变化,可是移动处理器如今已是双核和四核了,因此想提升性能不是那么容易。

摩尔定律不管怎么说均可能是正确的,可是这须要整个移动生态环境向x86环境转变。这并不是彻底不可能,毕竟曾经有人作过这样的事。但那是在移动处理器一年才卖出去100万个的时候作的,不像如今,每一个季度就能够卖出6200万个芯片。那个时候现成的虚拟化环境能够模拟出老架构的60%的速度,而按照如今的研究来看,虚拟化系统上运行优化过的(O3)ARM代码的速度已经接近27%了。

若是你坚信JavaScript的性能最终会到达一个合理的水平,那么硬件性能的提高绝对是最好的方式。要么Intel会在5年以内开发出可行的iPhone芯片(这是有可能的),而且苹果迅速转向x86架构(这是不太可能的),或者ARM可以在将来的10年以内获得性能的飞跃。可是在我看来,10年是一个很长的时间,长到足够使某件事情可能成功

恐怕个人硬件的知识只能分析到这里了。我能够告诉你的是,若是你相信ARM能够在将来的5年以内填补与x86之间的性能差距,那么第一步就是找到一个在ARM或者x86上工做的人(也就是真正懂硬件的人),让他赞成你的见解。我写这篇文章以前,曾近咨询过不少有很高资质的硬件工程师,他们全部人都拒绝公开发表这个观点,这让我感受这个观点不是很靠谱。

软件角度

这是一个不少优秀软件工程师犯错误的地方。他们的思路是这样的:JavaScript已经变得更快了,而且它会变得更快。

这个观点的前一部分是正确的,JavaScript的确变得快不少。可是咱们如今已经达到了JavaScript性能的顶点了,它不可能变得更快多少。

为何?其实前一部分JavaScript的性能提高从某种程度上是硬件的缘由,正如Jeff Atwood写道:

我感受从1996到2006之间JavaScript的性能变快了100倍。若是Web 2.0主要创建在JavaScript上的话,这极可能主要是由于摩尔定律所带来的硬件性能提高。

若是咱们把JS的性能提高总结为硬件性能提高的话,那么JS的已有的硬件性能提高不能预测将来的软件性能提高。这就是为何若是你相信JS会变得更快的话,最有可能的方式就是硬件变得更快,由于历史趋势就是如此。

那么JIT如何呢?V8,Nitro/SFX,TraceMonkey/IonMonkey,Chakra等等?固然,当他们刚刚问世的时候,的确是很了不得的(但或许不像你认为的那么了不得)。V8在2008年9月发布,我找到了一份差很少那个时候同期的Firefox 3.0.3,看看它的性能:

Screen Shot 2013-05-14 at 6.41.48 PM

不要误解个人意思,9倍的性能提高的确值得称赞,毕竟这差很少是ARM和x86之间的性能差距了。即使如此,Chrome 8和Chrome 26之间的性能却呈现出了水平线,由于自从2008开始,几乎没有什么重大的事情发生。其余浏览器厂商都已经遇上来了,有些快有些慢,可是没人真正提升过JavaScript的性能了。

JavaScript性能在提高吗?

Screen Shot 2013-05-14 at 3.59.04 AM

这是我Mac上的Chrome 8(可运行的最先版本,2010年12月份的版本)和Chrome 26

看不出差异?由于根本没有差异。JavaScript的性能最近根本没有获得大的提高

若是你感受如今的浏览器比2010年的浏览器跑的快的话,那极可能是由于你有了一台更快的电脑,可是这与Chrome的性能提高没什么关系。

更新:有些聪明的人指出SunSpider如今不是一个好的benchmark(而且拒绝提供任何实际的数字或其它什么)。为了可以能够理性地讨论,我在一些旧版本的Chrome上面运行Octane(一个Google的benchmark),的确显示出了一些性能提高:

Octane on V8, 2011 to 2013

在我看来,在这个期间的性能提高仍是过小,不足以支撑JS立刻就会足够快这样的论调。然而,要说我过度强调这个状况也没错,毕竟JavaScript的计算密集型操做的确在发生变化。可是推我来讲,这些数字能够得出更大的推断:这些性能提高的幅度还不足以在必定时间以内使得JavaScript的速度遇上原生代码。你须要性能达到2-9倍才能跟LLVM竞争。这些提高是好的,但还不足够好。更新结束

问题是,让JavaScript采用JIT技术是一个60年前就有的想法,而且这60年来一直有人在研究。数以千计的你可能想到的编程语言对JIT的实现都证实这是一个好主意。可是既然咱们已经作到了,咱们已经用完了这个60年前的想法。伙计们,就是这样的,表演结束了。或许咱们能够在将来的60年以内想到另外一个好办法。

可是Safari恐怕比之前要快吧?

Is Safari 7 3.8x faster than the other guys?

Safari 7 是否是比其它的浏览器快3.8倍?

这个结论或许对苹果来讲很容易获得,可是这个版本的Safari在NDA协议之下,因此没有人可以公开关于Safari性能的独立参数。可是我能够仅仅根据如今已经获得的信息来作一些分析。

我发现一些现象颇有意思。第1、苹果官方在公开的JSBench上的数据要比在他们在较老的benchmark(如SunSpider)上给出的数据高出很多。如今JSBench背后有一些很是酷的名字,包括JavaScript之父Brenden Eich。可是和传统的benchmark不同,JSBench的工做方式并非经过编写大整数分解或相似的程序,它反而自动为Amazon、Facebook和Twitter提供的内容进行优化,并且根据它们提供的内容来创建benchmark。

若是你在写一个多数人用来浏览Facebook的浏览器,我能够理解用一个只测试Facebook性能的benchmark是颇有用的。可是从另外的角度讲,若是你在写一个电子表格的程序,或者游戏,或是一个图像过滤应用,在我看来传统的benchmark(注重整数运算和md5哈希)会比分析Facebook性能的benchmark更可以准确地帮你预测出代码有多快。

另外一个重要的事实是,苹果声称的在SunSpider上性能的提高并不能表明其它东西的提高,Eich et al在这篇提到苹果所偏心的benchmark的文章中写道:

图中清楚地显示出了Firefox的3.6版本比1.5版本在SunSpider的benchmark上性能提升了13倍。可是当咱们看它在amazon的benchmark上的性能表现时,发现只有较适度的3倍的提高。更有意思的是,在过去的两年时间,在amazon的benchmark上的性能提高几乎已经不存在了。这意味着在SunSpider上作的一些优化几乎对amazon没有太大做用。

在这篇文章中,JavaScript之父和Mozilla的首席架构师之一曾公开认可在过去的两年以内Amazon的JavaScript性能几乎没有提高,没有发生过什么特别重要的事情。从这一点你也能够看出来,那些营销人员这些年都在过度夸大本身产品的性能。

他们继续争辩道:对于那些人们用来浏览Amazon网页的浏览器来讲,运行Amazon的benchmark比运行其它benchmark要更能准确地预测出浏览器的性能(这是固然了……),可是这些手段不会帮助你更好地写出一个照片处理程序。

可是不管怎么说,从我能够看到的公开信息来看,苹果声称的3.8倍的性能提高对你来讲几乎没有什么太有用的东西。我能够告诉你,若是我有一些可以反驳苹果声称击败Chrome的benchmark的话,我将不被容许发布它们。

因此,咱们总结一下这一节,若是有一些人拿出一个柱状图来显示网页浏览器变得更快了,那并不能真正说明整个JS变得更快了。

可是还有一个更大的问题。

并不是为性能而设计

JavaScript-the-good-parts

下面这段话出自于Herb Sutter,现代C++中最著名的人物之一:

在过去的20年里,有一种很难根除的文化基因——只要等到下一代的(包括JIT和静态)编译器出来,托管语言就会变得和原生语言同样高效。是的,我彻底但愿C#和Java编译器可以不断提升,包括JIT和类NGEN的静态编译器。可是,它们永远不会消除与原生代码之间的效率差距,有两个缘由:1、JIT编译不是主要问题。根本缘由更为基本:托管语言在编程人员的开发效率(当时的确是个问题)和程序的运行效率之间从设计上作了故意的妥协。特别的,托管语言选择选择在全部的程序上添加额外的性能开销,尽管你根本没有用到一个特性,你都会受到这个特性带来的额外的性能开销。主要的例子是assumption/reliance、垃圾回收、虚拟运行环境和元数据等功能在托管语言中是默认打开。固然还有其它的例子,好比托管代码中函数默认是virtual的,而C++代码中的函数是默认inline的。1盎司(译注:12盎司=1磅)的内联阻止(inlining prevention)抵得上1磅的去虚拟化优化(devirtualization optimization cure)。

下面这段话出自于Mono项目组的Miguel de Icaza,他是为数很少的“维护着一个主流JIT编译器的人”。他说道:

关于主流托管语言(.NET、Java和JavaScript)的虚拟机之间的差别,有一个比较准确的说法。托管语言的设计者在他们设计一门语言的过程当中更倾向于安全性,而不是性能。

或者你能够找Alex Gaynor谈一谈,他负责维护和优化Ruby的JIT编译器,而且也为Python的JIT的优化工做作出了贡献:

这是加在这些具备高生产力的动态语言身上的诅咒。它们使得建立一个哈希表十分容易。这是一件很是好的事情。我认为C程序员多数不太会使用哈希表,由于对他们来讲用哈希表实在是是一件痛苦的事情。缘由有二:第一,你没有一个内置的哈希表;第2、当你尝试去使用的时候,你会左右碰壁。对比来看,Python、Ruby和JavaScript程序员都过分使用哈希表了,由于使用它们实在太容易了……因此你们都不在意。

Google彷佛意识到了JavaScript正面临着性能的瓶颈:

复杂的Web应用(这是Google比较擅长的)在某些平台上正在面临着不小的挣扎,主要是由于这些应用用到了一些不能被性能调优的语言,这些语言有内在的性能问题。

最后,咱们听听权威人士的意见。个人一个读者向我指出这段Brenden Eich的评论。正如你所知,他是JavaScript之父。

有一点Mike没有强调:获得一个更简单的语言。Lua比JS简单得多。这意味着你能够写出一个简单的解释器使得它跑得足够快,同时可以保持对trace-JIT代码的尊重(这和JS不一样)。

稍微下面一点又提到:

关于JS和Lua之间的差异,你能够说这彻底是正确的设计和工程上的问题,可是内在的复杂性区别仍是很大。你固然能够把较难的案例从热路径中删除,可是他们也会所以付出代价。JS比Lua有更多的更难的案例。一个例子是:Lua(没有显式的元表使用)没有像JS中的原型对象链(prototype object chain)的东西。

在这些真正从事相关工做的人当中,持有JS或者是其它动态语言可以遇上C语言性能这个观点的,只占极少数(very much the minority)。处处都有和主流想法不一样的人,因此根本没法没有什么办法可以达到真正的一致。可是,从语言的角度说到JIT语言是否可以遇上原生语言的效率,他们给出的答案都是“不,不可能,除非修改语言自己或者API”。

可是还有一个更更大的问题。

都是由于垃圾回收

你能够发现,CPU问题、CPU相关的benchmark以及全部有关CPU的设计决定,都只是故事的一半。故事的另外一半是内存。内存问题如今看来是如此的巨大,大到使整个CPU的问题看上去都仅仅是冰山一角。实际上能够讨论的是,全部关于CPU的讨论都是转移注意力的话题(red herring)。你接下来要阅读的应该会彻底改变你对移动设备软件开发的理解。

2012年,苹果作了一件很是奇怪的事情(固然了,除非你是John Gruber,可以看到它的到来)。他们把垃圾回收从OSX中除去了。真的,你能够去看看程序员指南。标题右边有一个大大的“不推荐(Not Recommended)”。若是你以前是Ruby、Python、JavaScript、Java、C#或是其它任何1990年代以后诞生的语言的开发者,这应该会让你感受很奇怪。可是这极可能不会影响到你,由于你极可能不在Mac下面使用ObjC,在HN点击下一个连接。可是这仍然看上去很奇怪,毕竟GC一直被你们使用着,并且它的价值也获得了证实。为何你要反对它呢?苹果是这么说的:

咱们十分坚信ARC才是内存管理的正确方式,因此咱们决定使OSX上的垃圾回收变成过期的(deprecate)。——ession 101, Platforms Kickoff, 2012, ~01:13:50

这段话没有告诉你的是,当听到这句话的时候,台下的观众爆发出了热烈的掌声。OK,这就变得真的很是奇怪了。你是否是在告诉我有那么一个屋子里的程序员在为了垃圾回收以前的那种混乱的回归鼓掌?你能够想象下若是Matz在RubyConf上宣布GC过期的时候整个会场的寂静,几乎一颗针掉在地上都听获得声音。而这群人却所以而高兴?太古怪了吧?

你应该根据这些古怪的反应发现一些你如今看不到可是倒是在真正发生的事情,而不是仅仅把这些事情归结于这群人对于苹果的狂热。这些正在发生的事情就是咱们下面就要讨论的主题。

思惟过程是这样的:把一个工做得好好的垃圾回收器从一个语言中拿出来简直是疯了吧?一个简答的解释多是ARC可能仅仅是苹果为了给垃圾回收披上一层美丽新装而创造的一个营销词汇,因此这些开发者是为了这种升级而不是降级而鼓掌的。事实上,这就是不少iOS簇拥们的抱有的想法。

ARC不是一个垃圾收集器

全部的那些认为ARC是某种垃圾回收器的人,我想经过下面这个苹果的幻灯片给你迎头一击(beat your face):

Screen Shot 2013-05-14 at 9.44.43 PM

这与和垃圾回收有相似名字的算法无关。它不是垃圾回收,他不是什么像垃圾回收的东西,它表现得一点都不像垃圾回收,它不会打乱任何保留周期,它没有去回收任何东西,它甚至没有去作扫描。OK,故事结束,它绝对不是垃圾回收。

由于正式的文档还在NDA协议下,因此有不少传言认为这并非真的(可是细则已经能够看获得了,没有任何借口了),并且不少博客都纷纷说这些不是真的。它是真的。不要再讨论了。

垃圾回收不像你的经验让你感受的那样可行

这是苹果在压力之下给出的关于ARC和GC的说法:

在愿望清单的顶端上咱们能为大家作的最重要的事情就是把垃圾回收带到了iOS中,而这偏偏是咱们最不该该作的。不幸的是,垃圾回收给性能带来了不少次优的影响。你程序中的垃圾回收会使得你的内存使用率变得很高,并且垃圾回收器常常在不肯定的时间点上被触发而致使很是高的CPU使用率,从而打断用户正在作的事情。这就是为何GC不适合在咱们的移动平台上使用的缘由。对比来看,带有获取和释放(retain/release)的手动内存管理学起来比较难,坦率的讲有些像痔疮(译注:这个翻译可能不许确,原文是pain in the ass)。可是它产生了更好更可预测的性能,这也是为何咱们选择手动内存管理做为咱们内存管理策略的基础的缘由。由于在外面真实的世界,高性能以及用户体验的连续性是咱们的用户最看重的。(译注:在苹果看来,用户体验要比开发者体验重要。)~Session 300, Developer Tools Kickoff, 2011, 00:47:49

可是这仍是彻底疯狂了,不是吗?这只是开始:

1. 这可能会直接影响你整个职业生涯对于垃圾回收语言给桌面和服务器上带来影响的理解;

2. Windows Mobile、Andriod、Mono Touch以及全部其它移动平台上的GC彷佛均可以挺好地工做。

因此让咱们反过来看这个问题。

移动平台上的GC和桌面平台上的GC不是同一回事

我知道你在想什么,你是一个有了N年开发经验的Python程序员。如今是2013年了,垃圾回收彻底能够解决问题。

这是一篇你想看到的文章,彷佛问题并无解决:

Screen Shot 2013-05-14 at 10.15.29 PM

若是你在这篇文章中其它什么都记不得,那么请记住这张图。Y轴是垃圾回收所用的时间,X轴是“相对的内存足迹”,相对于什么?相对于所需的最小内存

这张图想说明的是,“若是你有6倍以上你实际须要的内存,那么使用垃圾回收是没有问题的。可是若是你只有小于4倍你实际须要的内存,那么灾难就要降临了。”。可是不要相信个人话:

特别的,若是垃圾回收时系统拥有5倍于所需的内存时,它的运行时性能差很少甚至是超过显式内存管理。可是,垃圾回收的性能在必须使用小堆(small heap)的状况下会出现急剧降低。若是有3倍于所需的内存的话,它会跑得慢17%;若是只有2倍于所需的内存的话,会慢70%。垃圾回收比物理内存的换页更容易受到内存不足的影响。在这种状况下,咱们所测试的全部垃圾回收器相对于手动内存管理都出现指数级的性能降低

如今咱们再来比较一下显式内存管理的策略:

这些图显示,若是可用内存在合理的范围的状况下(但不足以容得下整个应用),显式内存管理器都要比垃圾回收器快太多。好比说,pesudoJBB以63M的可用内存运行,Lea allocator在25s的时间内完成运行。在相同可用的内存下运行GenMS,花了超过10倍的时间来运行(255s)。咱们能够看到其它benchmark套件的相同趋势。最值得一提的例子是213 javac和Lea allocator一块儿在36M的内存下运行,整体运行时间是14s,而与GenMS一块儿运行的状况下,运行时间为211s,用了超过15倍的时间。

基本的事实是,在内存受限的环境下垃圾回收性能的降低是指数级的。若是你在桌面电脑上写Python或者JS程序的话,你整个的体验多是这幅图的右边部分,你能够一生都体会不到垃圾回收带来的性能问题。花点时间想一想这幅图的左边部分,而且想一想咱们该如何应对。

iOS上有多少可用内存?

这一点很难准确地描述。从iPhone 4到iPhone 5,这些设备上的物理内存从512M到1G不等。可是其中很大一部分为系统预留了,还有更大的一部分为多任务处理预留了。因此惟一真正的方法是在不一样的状况下进行尝试。Jan Ilavsky写了一个很是有用的工具来完成这个任务,可是貌似没有人公开任何数据。但这一点如今已经改变了。

如今,在一种“正常”的状况下(这一点很难具体说清楚是什么意思)进行测试是很是重要的,由于若是你在一台刚刚启动的机器上测试的话,你会获得更好的数据,毕竟你的系统里面没有Safari所打开的页面。因此我就在“真实世界”的状况下拿出一些我公寓里的设备进行本次benchmark。

Photo May 14, 10 51 13 PMPhoto May 14, 10 51 13 PM

你能够点击进去看看详细的结果,大致上来讲,在iPhone 4S上,当你的程序使用了40M内存的时候就会获得警告,而使用了213M内存的时候,程序就会被杀死;在iPad 3上,使用400M左右时得到警告,而使用550M左右的时候,程序被杀死。固然了,这些也仅仅是数字而已,若是你的用户在听音乐或者在后台跑一些程序,你可用的内存会比我测试里面可用的内存更少,这只是给你一个思路而已。这么多内存看上去很多(213M应该对每一个人来讲都足够了,是吧?),但实际上这还不够。举个例子,iPhone 4S拍照时的分辨率为3264*2448,每张照片有超过30M的位图数据。若是你在内存里加载了2张照片你就会得到警告,而若是加载了7张照片,程序就会被杀死。哦,你打算给你写个循环在你的相册中逐个处理?程序会被杀死。

还有一点须要十分注意:实际的状况下,一张照片可能存在于内存的多个位置。好比说,若是你在拍照片,那么你在如下位置都有数据:1) 你经过屏幕看到的摄像头中的数据,2) 摄像头实际上拍到的照片数据,3) 你尝试写到存储卡中的压缩JPEG缓冲数据;4) 你准备在下一个屏中显示的数据;5) 你准备上传到某个服务器中的数据。

在一些点上你会发现,保留30M的缓冲区去显示照片缩略图是一个很是很差的想法,由于这样你会引入更多的数据:6) 用来保留下一屏显示合适大小照片的缓存;7) 用于在后台从新调整照片大小的缓存(在前台作实在太慢了)。而后你发现你可能真正须要5个不一样的大小,而后你的程序就不是通常的慢了,而是慢到让人抓狂。在实际的应用程序中,仅仅是处理一个照片就会遇到内存的瓶颈并不是罕见的事情。可是不要相信我说的话:

你能作的最糟糕的事情就是在内存不充足的状况下在内存中缓存图片。当一张图片被画成位图或者显示到屏幕上时,咱们就不得不把照片解码为位图。位图的每一个像素点为4字节,不管原始图片多大都是如此。每当咱们将它解码一次,位图就会绑定到图片自己而且一直维持到这个对象生命周期结束之时。因此若是你把图片加载到内存并且曾经显示过一次,那你如今就会在内存中保留整个位图,直到你释放它为止。因此永远不要把UIImage或者CGImage放到缓存中,除非你有一个很是明确(希望是很是短时间的)的目标。- Session 318, iOS Performance In Depth, 2011

你甚至不要相信上面的话!你给本身分配的内存其实只是冰山一角。下图是苹果一张幻灯片中给出的冰山的全图。Session 242, iOS App Performance – Memory, 2012:

Screen Shot 2013-05-15 at 5.39.12 AM

你能够从两方面考虑这个问题。第1、在213M可用内存的状况下,在iOS上写一个照片处理程序比在桌面上写一个要困难许多。第二,你在iOS上写一个照片处理程序时,你对内存的须要会更多,由于你的桌面程序没有一个能够放进你口袋的摄像头

咱们能够看看另一个例子:在iPad 3上,你要显示一个视频,这种照片的大小极可能比你电脑上的视频要大很多(后面的高分辨率摄像头,差很少2000-4000像素)。每一帧要显示的就是一个12M的位图。若是你对内存的使用很节省的话,每一时刻你能够在内存中保留45帧的未压缩视频或动画缓存,也就是在30fps的状况下每1.5s,在60fps的状况下0.75s。你想为一个全屏的动画预留缓存?应用被杀死。值得指出的是,AirPlay的延迟是2s,因此对于任何多媒体类型的应用,你几乎是保证没有足够的内存

这种状况下咱们一样面临着和照片的多个数据拷贝差很少的问题。好比说,苹果指出,“每个UIView背后都有一个CALayer,并且只要CALayer存在于在这个层次中,对应的图片数据会一直保存在内存中”。这意味着,极可能有许多中间的渲染数据的拷贝存在于内存中。

还有剪切矩形和备份存储这些可能会占用内存的事情。这样的数据处理架构事实上是很是高效的,可是这带来的代价是程序会尽量地占用内存。iOS不是为低内存使用而设计的,它是为了快速运行而设计的。这没有和垃圾回收扯在一块儿。

咱们一样须要从两个方面考虑问题。第一,你在一种内存很是紧缺的状况下作出动画效果;第2、作出这样超级高质量的视频和动画是须要极大的内存的。而为了使得普通消费者买得起消费级别的、具备高摄像头分辨率的产品,这种糟糕的、内存受限的环境几乎是必然的选择。若是你想写一个软件来毫无压力地播放视频,那么你就得说服别人为了屏幕多花700美圆,或者花500美圆买一个iPad,它实际上已经包含了一个内置的电脑。

咱们会得到更多内存吗?(更新)

一些聪明的人说:“OK,你说了不少关于咱们不会有更快的CPU。可是咱们应该回有更多的内存吧?这正是桌面环境上发生的事情。”

这种理论的一个问题是,ARM平台上的内存就在处理器自己上,这被称为package on package。因此在ARM上得到更多的内存几乎和提升CPU性能是同一个问题,由于它们归根结底是同一件事情:在CPU上集成更多的晶体管。内存晶体管处理起来稍微容易一些,由于它们是统一的,因此不是那么难,但实际上也不是那么简单。

若是你看看iFixit的A6的照片,你会发如今CPU模具最表面的硅几乎100%都是内存。这意味着,若是你想拥有更多的内存,你要么使得制做工艺更精细,要么提升模具的大小。事实上,若是你把工艺的大小作归一化处理,那么其实伴随着每次内存升级,你的模具都在变得更大。

normalized die size for iPhone chips

硅实际上是一种不完美的材料,为了得到更大的尺寸所付出的的代价是指数级增加的。它们也很难维持较低的温度,也很难放进小设备中。它们和制做出更好的CPU的目标是重复的,由于内存也面临一样的问题:CPU最上层的硅中须要放进更多的晶体管。

搞不懂的是,面临着PoP的这些问题,CPU厂商们继续使用PoP的方式为系统提供内存。我尚未遇到任何ARM工程师可以解释这一点。或许如下的评论可能会帮助咱们理解。咱们有可能从PoP架构转向电脑中采用的分离内存模块,并且我感受这比较可行。缘由很简单,将内存分为单独的模块比制造出更大的芯片和进一步减少制做工艺对厂商来讲毫无疑问成本是更低的。可是如今全部的厂商都在不停地尝试提升制做工艺或者制做出更大的芯片,而不是把内存模块独立出来。

然而,一些聪明的工程师曾经给我发了邮件让我填补了这方面的空白。

一个前Intel工程师说道:

PoP内存模型能够大量减小内存延迟,也能够减轻路由问题。可是我不是ARM工程师,也不肯定这是不是所有的缘由。

一个机器人学的工程师提到:

当PoP内存不够用时,“3D”内存会提供足够大的内存:内存芯片在生产的时候堆叠在一块儿,1G的RAM在同一层堆成10层向上,就像如今的硬件模型同样。可是,这样的开销会很大,频率和电压都要相应地下降使得电力消耗处于一个合理的水平。

移动RAM的带宽不会像最近提升得这么快了。带宽被链接SoC和RAM包的总线的数量所限制。当前RAM的总线多数使用的是高性能SoC的圆柱体表面。SoC的中间部分不能用来加入RAM总线,由于这些RAM包是层叠的。接下来的重大改变应该回来自于将SoC和内存放在一个单独的、高度集成的包中,容许更小、更密集和大量的RAM总线(更大的带宽),并给SoC设计和更低的RAM电压带来更多自由。根据这样的设计,更大的缓存也就可能成为现实,由于RAM可能用更高的带宽放在SoC模具中。

可是Mono/Andriod/Windows Mobile平台怎么解决这个问题呢?

这个问题事实上有两个答案。第一个答案咱们能够从图中看出。若是你发现你有6倍于所需的内存,垃圾回收实际上是很是快的。举个例子来讲,若是你在写一个文本编辑器,你可能能够用35M的内存完成本身要作的全部事情,这是个人iPhone 4S会崩溃内存上界的1/6。你可能在Mono上写个文本编辑器,看到很是不错的性能,而后从这个例子中得出以下结论:垃圾回收十分适合这个任务。你是对的。

然而Xamarin框架在案例中有一个飞行器模拟器。很显然的是,垃圾回收对于现实生活中的较大的应用程序来讲是不适合的。难道不是吗?

你在开发和维护这个游戏时必定会遇到什么样的问题?“性能一直是一个大问题,并且将持续是咱们再跨平台中会遇到的最大的问题之一。最初的Windows Phone设备是很是慢的,咱们不得不花不少时间来优化程序,使得它达到一个体面的帧率。咱们不只仅在飞行模拟代码上进行优化,并且在3D引擎上优化。垃圾回收和GPU的弱点是最大的瓶颈。

程序员不约而同地声称垃圾回收是最大的瓶颈。当你的案例中的人在抱怨的时候,那应该是一个足够引发你重视的线索了。可是Xamarin多是一个局外人,咱们仍是来看看Andriod开发者怎么说的吧:

请记住下面是我在个人Galaxy Nexus上运行的状况:不管怎么说都是性能很是不错的设备。可是看看渲染的时间!我在电脑上只要花几百毫秒就能够渲染出这些图片,但是在这台手机上却花了超过两个数量级的时间。渲染“inferno”图片超过6s?这简直是疯了吧!要生成一副图片,须要10-15倍的时间来运行垃圾回收器。

一个开发者:

若是你想在Andriod手机上为实时物体识别或者基于内容的现实加强进行对照相机图片的处理,那么你极可能据说过照相机预览回调(Camera Preview Callback)的内存问题。每次Java程序尝试从系统获取预览图片时,系统就会建立一大块新的内存。当垃圾回收器释放这块内存时,系统会卡住(freezes)100ms到200ms。若是系统在高负载的状况下,事情可能会变得很是糟糕(我曾经在手机上作过物体识别——天呐,它几乎把整个CPU都占用了)。若是你看过Andriod 1.6的源代码你就会知道,这只是由于这个功能的包装类(wrapper,用来包装原生代码)每次在一个新的帧可用时都会申请一个新的字节数组。固然,内置的原生代码能够避免这个问题

或者,我还能够去看看Stack Overflow

我负责在Andriod平台上为Java写的交互式游戏进行性能调优。不少时候,当候垃圾回收开始工做会让使得游戏的画图和交互功能发生打嗝。一般状况下这种打嗝持续不到1/10s,但有的时候在比较慢的设备上会长达200ms。若是我在一个内部循环中使用树或者哈希表,我就就知道我要很当心,或者甚至不用Java标准的Collections框架,而是本身从新实现一个,由于我承担不起垃圾回收带来的额外开销。

这是一个“接受的答案(accepted answer)”,有27我的赞同:

我也是Java手机游戏的开发者……避免垃圾回收(垃圾回收可能在某个点被出发从而大幅下降你游戏的性能)的最好方法就是不要再游戏的主循环中建立对象。实在没有什么“简洁”的方式来处理这种问题,或许只有手动追踪这些对象了,真悲哀。这地也是目前大部分当前移动设备上性能优良的Java游戏所采起的的方式。

咱们来看看Facebook的Jon Perlow怎么看待这个问题:

对于开发流畅的安卓应用来讲,GC是一个很是大的性能问题。在Facebook,咱们遇到的最大问题之一是GC会使得UI线程暂停。当咱们处理不少位图数据时,GC被触发的频率很高,并且难以免。GC常常致使掉帧的问题。即便GC只会阻塞UI线程几毫秒,但这却会严重影响本来须要16毫秒的帧渲染。

OK,咱们再听听一个微软MVP的说法:

一般状况下,你的代码在33.33ms以内就会完成执行,从而使得30fps的帧率变得很不错。可是当GC运行的时候,它会占用那个时间。若是你的堆比较整洁和简单,那么GC通常能够运行得不错,不会对程序产生什么影响。可是让一个简单的堆处于一个使GC能够快速运行的情形是一件困难的编程任务,它要求大量的计划和/或程序重写,即便是这样也不是彻底安全的(有时在一个复杂的、有不少玩意的游戏中你的堆里面有不少内容)。更简单的方法是(假设你能这么作),在游戏过程当中限制甚至是禁用内存分配。

在有垃圾回收的状况下,在游戏中保证必定会赢的方法就是不要玩(做者的幽默,原文:the winning move is not play)。比这种哲理稍弱的一种形式如Andriod官方文档中所说的:

对象建立永远不是免费的。带有线程级别内存池的垃圾回收器使得内存分配的成本变得稍微低一些,可是分配内存永远比不分配内存开销大。由于当你在程序中分配对象时,你会强制垃圾回收周期性地工做,从而使得用户体验不是那么流畅。Andriod 2.3当前引入的垃圾回收器有一些做用,可是要是应该避免没必要要的工做。所以,你应该避免建立你不须要的对象实例。通常而言,尽量不要建立短时间临时对象。越少的对象建立就意味着越低频率的垃圾回收,从而提升用户体验。

还不信?那让我来问问一位真正从事垃圾回收工做的工程师,他为移动设备实现垃圾回收器。

然而,WP7系统的手机CPU和内存性能正在大幅度地提高。游戏和大型Silverlight应用愈来愈多,这些程序会占用100M左右的内存。随着内存的变得愈来愈大,不少对象拥有的引用会指数级地变多。在上面解释的模式中,GC不得不去遍历每一个对象以及他们的引用,标记它们,而后清理没哟引用指向的内存。全部GC的时间也大幅增长,而且成为这个应用的工做集(workingset)的一个函数。这会在大型XN游戏中和SL应用中致使程序卡住,体如今很长的启动时间(由于GC有可能在游戏启动的时候运行)或者游戏过程当中的小问题。

仍是不信?Chrome有一个测量GC性能的benchmark。咱们来看看它都干吗了:

Photo May 15, 2 19 26 AM

你能够看到不少GC致使的卡顿。固然了,这是一个压力测试,但仍是能说明问题的。你真的愿意花几秒时间来渲染一帧?你疯了吧。

这么多引用,我才不会挨个看呢,直接告诉我结论就好了。

结论是:移动设备上的内存管理很难。iOS平台的开发者已经造成了一种文化,即手动作大部分事情,让编译器作其它容易的部分。Andriod平台造成的文化是,提升垃圾收集器的性能,但事实上开发者在实际开发中尽可能避免使用它。这二者的共同点是,你们在开发移动应用时,开始愈来愈多地考虑内存管理问题了。

当JavaScript、Ruby或是Python开发者听到“垃圾收集器”这个词时,他们习惯将它理解为“银弹(silver bullet)垃圾收集器”,也就是“让我不要让我再考虑内存问题的垃圾收集器”。可是移动设备上根被没有银弹可言,每一个人写移动应用时都在考虑内存问题,无论他们是否使用了垃圾收集器。得到“银弹”内存管理方式的惟一方式就像咱们在桌面环境上同样,拥有10倍于程序实际须要内存。

JavaScript的整个设计基于一个思想,即不要担忧内存。看看Chromium开发者们怎么说

有没有任何一种方式强制chrome的js引擎进行垃圾回收?通常意义而言,没有,这是从设计的角度就已经肯定了的。

ECMAScript规范没有提到“分配(allocation)”这个词,惟一与“内存”相关的话题本质上就是说整个主题都是“实现相关的(host-defined)”。

ECMA 6的维基页面上有几页提案的草稿,归根结底是说(不是开玩笑):

“垃圾回收器不能够回收那些程序须要继续使用来完成正确执行的内存。全部不能从根节点传递遍历到的对象都应该最终被销毁,防止程序由于内存耗尽而发生错误。”

是的,他们的确在思考将这个需求规约:垃圾回收器不该该回收那些不该该被回收的东西,可是应该回收那些须要被回收的。欢迎来到tautology club。可是下面这段话可能与咱们的话题更相关:

然而,并无规范说明单个对象占用多少内存,也不太可能会有。所以当任何程序在内存耗尽的状况下,咱们永远不会获得任何保证,因此任何准确的、可观察的下界。

用英语来讲就是,JavaScript的思想(若是这算是一种思想的话)是你不该该可以观察到系统内存中的状况,想都不要想。这种思想和人们在写实际的程序时候的想法简直是使人难以置信的背道而驰(so unbelievably out of touch),我甚至找不到正确的词语来向你形容。个人意思是,在iOS的世界里,咱们并不相信垃圾回收器,咱们感受Andriod开发者都疯了(nuts)。我怀疑Andriod开发者会这么认为:iOS开发者居然会用手动内存管理,简直是疯子。可是你知道这两个水火不容的阵营的人能够在哪件事情上达成共识吗?那就是JavaScript开发者是真正的疯子。你在移动平台上写出一个有点意思的程序,而历来不关心系统内存的分配和释放,是绝对不可能的(absolutely zero chance)。绝对不可能。暂时把SunSpider的benchmark上的问题和CPU计算密集型的问题都抛开,咱们能够得出这样的结论:JavaScript,尽管如今存在着,是和移动平台软件开发过程当中绝对重要的思想,即永远要考虑内存问题,从根本上是背道而驰的

只要人们想要人们想在移动设备上开发各类视频和照片处理程序(不像桌面电脑),只要移动设备的内存不是那么充足,这个问题就是很是棘手的。你在移动设备上须要理性的、正式的内存管理保证。而JavaScript从设计上来讲是拒绝提供这些的

假设它可以提供这些

如今你可能会问,“OK,桌面环境上的JS开发者不会移动设备上的开发者遇到的问题。假设他们相信你说的,或者假设有一些知道这些问题的移动开发者们根据JS从新设计一门语言。你感受理论上他们能够作哪些事情?”

我不肯定这是不是解决的,可是我能够在这个问题上放一些边界。有另外一群人曾经尝试在JS的基础上设计一门适合移动开发者的语言——RubyMotion

这些人很是聪明,他们很了解Ruby。而后这些Ruby开发者认为垃圾回收对于他们的语言来讲是一个糟糕的想法。(GC倡导者们,大家看到我说的了吗?)因此他们用了一种很是相似ARC的技术而后嫁接到语言当中,然而却没有成功

总结:不少人正经历着因为RM-3或者其它难以辨别的问题所致使的内存相关议题,咱们能够看看他们怎么说。

Ben Sheldon

不只仅是你,我也面临着内存相关的程序崩溃(好比SIGSEGV和SIGBUS)生产环境下有10-20%的用户遇到过这种状况。

有一些人怀疑这个问题是否易于处理:

我在最近的一次Motion Meetup会上提出关于RM-3的问题,Laurent和Watson都对此提出了本身的见解。Watson提到说,RM-3是最难修复的bug;Laurent说他尝试了不少方法,但最终都没有很好地解决这个问题。他们两我的都是很是聪明和厉害的程序员,因此我相信他们说的话。

还有一些人怀疑编译器理论上是否可以解决这个问题:

很长的一段时间内,我都认为编译器能够简单明确地处理程序块,即静态地分析程序块内部的内容来判断程序块是否引用了这个程序块外部的变量。我认为,对于全部这些变量,编译器能够在程序块建立时获取,在程序块销毁时释放。这个过程吧这些变量的生命周期绑定到程序块上(固然,在某种状况下不是“完整的”生命周期)。有一个问题是instance_eval(译注:Ruby中Object类的方法)。程序块中的内容或许是按你提早知道的方式使用的,但也有可能并非你可以提早知道的。

RubyMotion还有一个对立的问题:内存泄露,并且它还有可能有其它问题。没有人真正知道程序崩溃时内存泄露有2个缘由仍是有200个缘由。

因此无论怎么说,咱们的结论是:一部分世界上最好的Ruby程序员专门为移动设备开发设计了一种语言,他们设计了一个系统,这个系统不只会崩溃,并且还会内存泄露,这些问题都是你可能会面临到的。至今为止他们并无可以处理这个问题,尽管他们已经很是尽力了。对了,他们也表示他们“本身尝试了很多次,但没有可以可以找到一个好的而且可以保持高性能的解决方案”。

我并非说在JavaScript的基础上建立一门具备较高内存性能的语言是不可能的,我只是想说不少证据显示这个问题会很是难

更新:一个Rust语言的贡献者提到:

我为Rust项目工做,个人主要目标是实现零额外开销的内存安全。咱们经过“@-boxes”(@T声明的类型是任何类型T)的方式来支持经过GC处理的对象,而咱们最近遇到的比较麻烦的事情是,GC触碰到语言中的全部内容。若是你想支持GC但却不须要它,你就要很是仔细地设计你的语言来支持零额外开销的非GC指针。这不是一个简单的问题,我不认为能够经过创建在JS的基础上建立一门新语言来解决

OK,可是ASM.JS如何呢?

asm.js就比较有趣了,由于它提供了一个JavaScript模型,但这个模型严格意义上不是创建在垃圾回收的基础上的。因此从理论上讲,使用正确的网页浏览器,使用正确的API就能够了。问题是,“咱们会获得正确的浏览器吗?”

Mozilla显然在这个概念上被出卖了,做为这个技术的做者,他们的实现今年晚些时候实现了它。Chrome的反应一直是含糊不清的,由于这个技术显然和Google的其它提案,包括Dart和PNaC1有直接的竞争关系。关于它有一个开发的bug清单,可是一个V8的黑客对此不满意。至于Apple阵营,按照我如今看来,WebKit那群人对比彻底保持沉默。IE?我历来就没有抱任何但愿。

不管如何,如今还不能说asm.js就是真正解决JavaScript问题,且可以击败全部其它提案的方法。另外,若是它能作到,它真的不多是JavaScript,毕竟它可以可行的缘由就是抛开了麻烦的垃圾回收器。因此它有可能和C/C++或者其它手动管理内存的语言的前端一块儿工做,但确定不和咱们如今知道而且喜欢的动态语言同样。

相对什么来讲慢?

当一些文章里面说“X慢”和“X不慢”的时候,一个问题是,没有人真正说的清楚它们的参照系是什么。对于一个网页浏览器开发者,和对于一个高性能集群的开发者,以及对于一个嵌入式系统的开发者,等等,“慢”的含义是不同的。既然咱们已经闯过了战壕并且作了这么多benchmark,我能够给出你三个有用大体正确坐标系

若是你是一个Web开发者,把iPhone 4S的Nitro当作IE8来看,由于它们的benchmark成绩差很少。这就给了你写代码时的正确坐标系。写代码的时候应该谨慎地使用JS,不然你会面临一大堆平台相关的性能问题要处理。有些应用用JS来写性价比是不高的,即便是流行的浏览器。

若是你是x86平台上的C/C++开发者,把iPhone 4S的Web开发环境当成只有桌面开发环境性能的1/50。其中1/10来自于ARM相对于x86的性能差距,1/5来自于JavaScript相对于C/C++的性能差距。在非JavaScript、性能为桌面环境1/10的状况下,仔细考虑正反面的因素。

若是你是Java、Ruby、Python或者C#开发者,按照如下方式去理解iPhone 4S的Web开发环境:它的性能是你电脑的1/10(ARM的因素),而且若是你的内存使用超过35M,性能会指数级降低,这是由垃圾回收的工做方式决定的。还有,若是你的程序分配了超过213M的内存,程序就会崩溃。注意,没有人“从设计的角度”在运行时刻给你这个信息。对了,人们都但愿你在这种环境下写出很耗内存的照片处理和视频应用。

这是一篇很是长的文章

下面是你应该记得的内容:

  • 2013年,用JavaScript写的移动应用(如照片编辑等)实在是太慢了。
    • 比原生代码慢5倍
    • 性能和IE8差很少
    • 比x86平台上的C/C++代码慢50倍
    • 若是你的程序全部的内存不超过35M,比服务器端的Java/Ruby/Python/C#慢10倍;若是内存使用超过这个数,性能开始指数级降低
  • 要使这个速度变得快一些,最可能的方式是让硬件性能达到桌面水平的性能。从长远来看这是可行的,可是看起来要等很长时间。
  • 最近一段时间JavaScript语言自己并无变得更快,在JavaScript上工做的人认为,在现有的语言和API下,它永远不会向原生代码那么快。
  • 垃圾回收在内存受限的环境下会呈现指数级的性能降低,这一点比桌面和服务器级别的状况差不少。
  • 任何能干的移动开发者都花不少时间来为目标设备考虑内存性能问题,无论他们是否使用具备GC的环境。
  • 当前的JavaScript,从本质上是和容许程序为目标设备考虑内存性能问题这一点背道而驰的。
  • 若是JavaScript的工做者们意识到这问题而且作出改变,容许开发者考虑内存问题,经验代表这是技术上的难题。
  • asm.js让人看到了一些但愿,可是就算它能成功,它应该是用了C/C++或者相似的“过期的”语言的前端,而不是像JavaScript这样的前端。

让咱们提升争论的层次

毫无疑问的一点是,我不久就将收到上百封邮件,这些邮件圈出我说的某句话,而后在不提供任何实际的证据(或者根本不能算是证据)的状况下,指出我说得不对。或者说“我曾经用JavaScript写过一个文本编辑器,挺好”,或者说“有些我历来没见过的人写了一个飞行模拟器,可是历来没有给我写邮件说明他们遇到性能问题”,这些邮件我会一概删除。

若是咱们想要在移动Web开发(或者是原生应用,或者是任何其它事情)上取得一些进展,咱们都须要各类至少看上去有说服力(at least appear to have a plausible basis)的讨论,包括benchmark、期刊以及编译器做者们的引用等等。网上有不少HN关于“我曾经写了一个Web应用,挺好”的评论,还有不少关于Facebook在知道他们将会知道如今应该知道的东西的状况下(译注:原文knowing what they would have known then what they could have known now,不知这样翻译对不对?)是选择HTML5仍是原生应用是对是错的争论(译注:原文为bikeshedding,这是一个比较有意思的词,意思是在还没完成自行车车架还没弄好的状况下就去讨论车的颜色,意指过于关心细节和边缘的问题,而忽视主要问题)。

对于咱们来讲,剩下来的任务是,明确地量化如何使得移动Web和原生生态环境变得愈来愈好,接着为此作出一些事情。正如你所知,这也是一个软件开发者应该作的事情。


翻译到此结束,翻译过程前先后后花了4天的时候。当快回宿舍以前,终于能够放到博客上去时,居然感受有些难以想象。谢谢你们支持!