订阅程序员杂志RSS CSDN首页> 程序员杂志

为什么Web App的运行速度慢

发表于2013-09-16 18:47| 次阅读| 来源《程序员》| 0 条评论| 作者Steffen Itterheim

摘要:造成Web App运行速度慢的原因有很多,人们对此的观点也各有不同。本文谈到JavaScript的性能未来不会有所提高,建议开发者多考虑内存管理,同时也给不同开发者一些实际的参照范围。

SunSpider基准测试表明,现在移动Web应用的运行速度普遍较慢(如图1所示)。 基本上,人们对于SunSpider基准测试的态度主要有以下三类。

  • JavaScript比原生代码慢已不是什么新闻,现在的问题是,JavaScript在某些方面有点慢,对于你正在编写的软件类型是否至关重要。
  • JavaScript的运行速度对程序确实有所影响,但它正变得越来越快,总会有一天会甩掉“慢”的帽子。
  • 我用Python/PHP/Ruby来编写服务器端代码,我的服务器比你们的移动设备运行速度快。但假如我用解释型语言可以很好地支持上千个左右的用户,你们肯定能想出使用高性能的JIT语言支持单个用户的方法吗?这样做的难度有多大?


                                           图1 JavaScript程序在不同浏览器上的运行速度

在本篇文章中,我会做一个大胆的尝试——反驳上述的三个观点。JavaScript运行起来的确很慢,而且将来也没有变快的倾向;在服务器端编程的经验看似 很牛,但当你试图思考移动应用的性能时,却使你无法“从小处着眼”。此外,在谈及JavaScript的文章中,很少有人真正量化它到底有多慢,或提供一 套评判标准。为了纠正这一点,我会为JavaScript的性能提供三个当量,让大家对它有一个直观的认知。

与原生程序的性能进行相比,JS的表现究竟如何?

为了回答这个问题,我从Benchmarks Game中任意选取了一个基准测试。然后,找了一个很老的执行这个测试的C语言程序。我用iPhone 4S对LLVM(底层虚拟机)和Nitro进行对比测试。测试中,LLVM的速度始终是Nitro的4.5倍(如图2所示)。

由于现实中运行的代码都是任意的,所以这类实验才很有价值,你也不妨亲自做一做这样的实验。如果你好奇“如果CPU绑定函数不在Nitro JS中,而是放在原生代码中,速度会快多少”,我的回答是快5倍左右。这个答案与Benchmarks Game测试x86/GCC/V8的结果大体一致(GCC/x86一般比V8/x86快2~9倍)。

图2 LLVM与Nitro对比测试的结果

只有原生代码1/5的表现,是否能满足所有人?

密集型CPU是如何渲染电子表格的?其实没那么难。问题是,ARM不是x86。根据GeekBench的测试结果显示,最新的MBP与最新的iPhone相比,全系数差异为10,所以电子表格的渲染是没什么问题的。但文字处理的情况又怎么样呢?我们难道不能像在m68k(摩托罗拉68000的CPU)上那样,通过在其后面绑定一个协同处理器来操作吗?你也许忘了,Google Docs的实时协作功能,其实不是它的一种特性,这项功能是在2010年4月大规模改写时添加进来的。有了这项功能,iPhone 4S相对于Web浏览器完全没有竞争力。

让我们再来看看另一个重要的JavaScript应用程序——Google Wave。Google Wave从来不支持 IE8,因为IE8太慢了。所有受支持Google Wave的浏览器(Gogle Chrome、Safari 4、Firefox)的基准测试结果都低于1000毫秒。而iPhone的基准测试结果是2400毫秒,因此,它无法运行Google Wave。这里要说明一点:实时协作可以在移动设备上执行,却不可以用JavaScript执行。原生应用和Web应用之间的性能差距与FireFox和 IE8之间的性能差距相当,对于重要的工作来说,这个差距相当大了。

V8/Modern JS的性能是否可与C语言相媲美?

这个问题的答案是相对的。如果C语言程序的执行时间为10毫秒,那么一个50毫秒的JavaScript程序的性能是接近于C语言的。如果C语言程序的执行时间为10秒,那么一个50秒的JavaScript的程序,对于大多数人来说可能就不太容易接受了。

硬件角度

在x86上,运行速度为5毫秒是没问题的,因为x86比ARM快10倍,如果你让ARM的速度达到x86的水准的话,那么,你无须做任何工作就可以得到像桌面JS一样的性能。其实,iPhone 5令人印象深刻的很大一部分原因就是,苹果公司将原本45纳米的芯片缩小到了32纳米。但如果想续写神话,苹果公司必须将芯片进一步缩小到22纳米。假如英特尔公司的Bay Trail(22纳米的x86 Atom版本)已经面市,那么英特尔公司就必须为其发明一种全新的晶体管,因为普通的晶体管在22纳米的芯片上不适用。事实上,将ARM芯片缩小到28纳米、22纳米、甚至20纳米的计划,在2014年都将会付诸实行。从纯硬件的角度来看,具有x86级性能的x86芯片,会比具有x86级性能的ARM芯片放入智能手机更指日可待。

软件角度

很多优秀的软件工程师都有这样一个误解:JavaScript已经变得很快,它会变得更快。JavaScript确实已经快了很多,但我不认为它今后可以变得更快。为什么?一个原因是,在JavaScript的历史中,大部 分改进实际上都是基于硬件方面的。StackOverFlow创始人Jeff Atwood曾写道:我发现,JavaScript的性能在1996-2006年间提高了一百倍。如果在JavaScript的基础上构建Web 2.0,那么其可行的原因,很大程度上是因为摩尔定律的性能改进。

JavaScript在改进吗?

图3是在我的Mac上使用的Chrome V8,目前已经是第26版。与CPU绑定的JavaScript在2010年以后,就没有什么重大改善。如果你觉得Web比2010年更快,那可能是因为 你使用了一台速度更快的计算机,但它与Chrome的改进没有任何关系。这里插一句,有些人指出,SunSpider并不是一个好的基准测试,为了公平起 见,我在一些老版本的Chrome上也运行Octane(一个Google的基准测试),结果确实显示了一些改进(如图4所示)。

图3 用SunSpider对Chrome V8进行测试

我认为, 由于性能提高的幅度太小,测试结果无法证明JS的性能可能会提高的说法。但不得不承认,与CPU绑定的JavaScript并不是毫无改进。但这些数字却 证实了另一个更大的推论:无论什么时候,这些改进并不足以缩小JavaScript与原生代码间的差距,真要与LLVM相抗衡,性能上起码得整体提高 2~9倍才行。这就是说,虽然这些改进看上去很美,但还不足以令人满意。


图4 用SunSpider对Chrome V8进行测试

Safari比以前更快?

似乎每隔一周就会有人宣称,JavaScript在某个基准测试中实现了速度激增。苹果公司就声称,在JSBench测试中,JavaScript的速度提 高了3.8倍。这是真的吗?Safari因为带有苹果公司的DNA,所以其他人很难找出关于其性能的独立数据,但我们可以对一些已公开的信息做一番观察。 首先,苹果公司公布的Safari在JSBench上的测试结果,比在SunSpider等传统测试的结果要高得多。但与传统的基准测试不同的 是,JSBench的工作方式不是编写一个分解整数或执行类似操作的程序。相反,JSBench自动删除Amazon、Facebook和Twitter 所提供的数据。如果你编写一个电子表格、游戏或图像过滤应用的话,与查看Facebook的分析代码可运行的速度相比,一个具有整数算法和md5散列算法 的传统基准测试似乎更有预见性。

另一个重要事实是,苹果公司宣称,SunSpider上的某项改进不一定意味着其他地方也已改进。在介绍苹 果公司首选的基准测试的文章中,JavaScript创始人Brendan Eich写道:根据SunSpider的数据,Firefox的性能在V1.5与V3.6之间改进了13倍。而当我们查看Amazon上的性能改进时,仅 为3倍。而更有趣的是,在过去的两年间,Amazon上的性能提升已扁平化。这意味着,在SunSpider上的一些效果显著的优化对Amazon收效甚微。在那篇文章中,Brendan Eich和Mozilla的一位顶级架构师公开承认,Amazon的JavaScript性能在两年内毫无变化。无论如何,苹果公司声明的Safari 7的速度是其他浏览器的3.8倍,在体验上不一定会对你造成什么影响。我还可以告诉你,如果有某项测试驳倒了Safari击败Chrome的结果,也不会 获准发布。所以,我们可以得出这样的结论,某些Web浏览器数值上的速度提高,不一定意味着JS在整体上变得更快。

C++标准委员会主席 Herb Sutter曾说:20世纪90年代有一种难以扼杀的文化基因——只需等下一代JIT或静态编译器出现,托管语言就会发挥效用。我非常期待C#和Java 编译器不断改进,然而,这些改进并不会消除它们与原生代码之间的效率差异。主要原因在于,JIT编译并不是造成差距的主要问题,根本原因是,为了使程序员 的生产力最大化,托管语言在设计上特意进行了权衡,甚至不惜以牺牲性能效率为代价。比如对始终运行或默认打开的垃圾收集,以及虚拟机运行时间和元数据的假 设/依赖。自由软件的狂热支持者Miguel de Icaza对这段话十分认同,他说:这是对于主流托管语言的一个十分准确的陈述,托管语言的设计者早已将安全而不是性能放在设计的首位。对实际实施相关工 作的人而言,认为JS或一般的动态语言将赶超C语言的只有极少数人。

关于垃圾的收集

上文谈到CPU的问题,包括一些与CPU绑定的测试及设计之外,还有另一个更为重要的问题需要我们进一步进行讨论,那就是内存。

2012年,苹果公司做了一件奇怪的事情,即从OS X中删除了垃圾收集器。如果你过去使用Ruby、Python、JavaScript、Java、C#或任何20世纪90年代的语言,都会觉得此举非常奇怪。毕竟这个功能已存在了很长时间,苹果为什么要放弃它呢?苹果公司的回答是:我们强烈地感觉到ARC才是内存管理的正确方法,以至于我们决定在OS X中放弃垃圾收集器。更反常的是,观众听到这番陈述后爆发出了欢呼声,满屋的开发人员对回到垃圾收集器之前的混乱时期鼓掌欢迎。试想,如果Matz在 Ruby大会上宣布放弃垃圾收集器,会场会是多么寂静。所以我们应该这么想,ARC只是苹果公司针对一种特殊垃圾收集器的市场宣传而已。

但事实是,ARC不是垃圾收集器,它与具有类似名称的垃圾收集算法毫不相干。它没有垃圾收集器那么强大,没有打破保留周期,不会删除任何内容,不会扫描任何内容,也不会执行垃圾收集器有关的任何功能。总之,它不是垃圾收集器。即便是,也没有你想象的那样方便可行。

关于ARC与垃圾收集器的对比,苹果公司这样说:你们最希望我们做的事是将垃圾收集带到iOS中。而这正是我们不会做的。垃圾收集可能导致性能受损,垃圾可 能在应用程序中积累,升高你的内存使用率。而且,垃圾收集器倾向于在不确定的时刻执行,这可能导致非常高的CPU使用率和间断的用户体验,这正是我们的移 动平台不接受垃圾收集的原因。相比之下,手动内存管理也很难学习,而且是一件不讨人喜欢的事。但它会带来更好的性能,这正是我们选择它作为内存管理战略的 原因。因为在实际情况下,高性能和流畅的用户体验对用户至关重要。

移动设备上的垃圾收集器与桌面上的不是一回事

也许你以为垃圾收集已经是一个成熟的技术,但事实并非如此。事实证明,只有拥有实际所需的6倍的内存,运行才能顺畅无阻。但如果你的内存少于所需内存的4倍,你就悲剧了。事实上,在内存受限的环境中,垃圾收集性能会呈指数级下降。如果你编写的是在桌面上运行的Python、Ruby或JavaScript 程序,垃圾收集器可能始终都不会出现。

那么,iOS上有多少可用内存呢?这个问题很难回答,因为设备上的物理内存差异巨大。想要获得准确数字的唯一方法,就是在不同条件下对其进行测试。在iPhone 4S上,你会在内存减少到40MB时看到警告,并释放大约213MB内存;iPad 3会在内存减少到400MB时发出警告,并释放大约550MB内存。当然,如果你在听音乐或在后台运行程序,那么释放的内存可能比我的测试少得多。看上去 213MB似乎已经很大了,但实际并非如此。比如iPhone 4S拍摄分辨率为3264×2448的照片时,每张照片相当于3000万字节。内存会在只有两张照片的容量时发出警告。你打算写一段“For循环”对相册 进行迭代?别枉费心机了。

同样值得强调的是,在实际使用中,你常常将同一张照片放在内存中的多个位置。例如:1. 显示照相机所捕捉情景的照相机屏幕;2. 填入JPEG压缩数据来写入磁盘的缓冲区;3. 准备在下一屏中显示;4. 上传到某个服务器;5. 留出30MB缓冲区来显示一个照片缩略图有点不切实际,所以你会将一张较小的照片保存在适合在下一屏上显示的缓冲区中;6. 在后台调整照片的缓冲区。虽然在实际情况中,有时也会有处理单张照片就遇到内存限制的情形,但你自己分配的内存量只是冰山一角,其他内存还包括代码和全局 变量、线程堆栈、图像数据、CALayer备份存储、数据库缓存等。此外,你不仅有213MB可用的RAM内存,而且还有很多编写照片处理应用程序的需 求。

我们看看另一个示例,iPad 3的像素可能比你桌面显示屏的像素要高。在该显示屏上显示的每一帧都是一个12MB的位图。如果要善用内存,你可以在内存中一次性存储大约45帧的未压缩 视频或动画缓冲区(帧率约为30fps的1.5秒视频,或是系统频率为的60Hz的0.75秒视频)。但如果意外缓存了一秒的全屏动画,应用程序则会停止 工作。苹果公司表示:每个UIView的背后都有一个CALayer,只要CALayer仍在分层结构中,图像就会作为图层内容保留在内存中。从本质上来 说,这意味着你的视图分层结构可能有许多中间呈现画面(也就是副本)存储在内存中。而且,还有剪切矩形和备份存储等因素。只要CPU时间够用,它就是一种 非常高效的架构,但实现该性能的代价是消耗尽可能多的内存。要知道,iOS不是针对低内存消耗而设计的,而是针对快速运行而优化,不要将它与垃圾收集混 淆。

我们是否会获得更多内存

有些人自作聪明地认为,既然我 们无法得到更快的CPU,我们总能获得更多的内存吧,毕竟这在桌面上已经实现了。但不要忘了,如果使用ARM芯片,内存是在处理器自身上的,也就是我们常说的“层叠封装”。所以在ARM上获得更多内存,实际上非常类似于改进CPU的问题。因为它们最终可归纳为相同的结论,即在CPU程序包上打包更多的晶体 管。如果认真分析A6处理器,你会发现CPU模具上最顶部的硅片几乎全是内存。这意味着,要获得更多内存,就需要一种制程微缩技术或是一个更大的模具。事 实上,如果将制程大小标准化,那么“模具”在每次升级内存时都会变得更大。

正如上文所说,如果你拥有的内存是所需内存的6倍,那么垃圾收集就会非常快地运行。但重点在于,在移动设备上进行内存管理很难。iOS已形成了一种固定模式,靠手动执行大部分操作,让编译器执行简单的操作。 Android的模式在于对垃圾收集器进行改进。但无论如何,每个人在编写移动应用时,都会花很多时间思考内存管理问题。当JavaScript、 Ruby、Python程序员听到“垃圾收集器”时,他们的理解是“能让我们无须思考内存管理的垃圾收集器”,但这个想法是错误的。JavaScript 的理念是,最好是你自始至终都不要观察到系统内存的变化,这与人们在实际编程时的情况背道而驰。你需要在移动设备上进行严肃、正式的内存管理。

相对于什么而言比较慢

在讨论运行速度的文章中,没有人真正表明他们的参照范围是什么。如果你是Web开发人员,那么“慢”的含义与高性能集群开发人员所想的有所不同,与嵌入式开发人员所想的也不同。在我克服重重困难执行了基准测试之后,我可以提供3个既有用又大致正确的参照范围。第一,如果你是Web开发人员,则可将 iPhone 4S Nitro视为IE8,因为它在同一个类别中执行基准测试,会使你在为其编写代码时拥有正确的思想框架。JavaScript应尽量少用,否则将会遇到很 多针对平台的攻击。第二,如果你是x86 C/C++开发人员,则可将iPhone 4S Web视为一种以其桌面环境1/50的速度运行的C语言环境。根据测试,ARM会带来10倍的性能损失,而JavaScript会使其雪上加霜。第三,如 果你是Java、Ruby、Python、C#开发人员,则可认为iPhone 4S Web是一个运行速度比你想象慢10倍(原因在于ARM)的计算机,而且如果内存使用超过35MB,性能就会呈指数级下降,因为这是垃圾收集器在该平台上 的行为方式。另外,如果分配了213MB,应用程序将被关闭。

结束语

本篇文章的要点在于:JavaScript对于2013年的移动应用程序而言速度较慢(比原生代码约慢5倍、与IE8不相上下、比x86 C/C++慢约50倍,如果程序只有35MB,那么它比服务器端Java/Ruby/Python/C#慢10倍,而且它的性能会从此开始呈指数级下降);让JavaScript变得更快最可行的方法是,将硬件提升到桌面级性能(从长期来看这也许行得通但似乎要等很长时间);该语言本身目前似乎并没有变快的迹象;垃圾收集在内存受限环境中的性能会显著下降,这比桌面级或服务器级环境中的垃圾收集要糟糕得多,每个有能力的移动开发人员都要花大量时间考虑内存性能。

(原文作者:Steffen Itterheim,《Cocos2D学习》、《iOS游戏开发者》作者,Cocos2D、Kobold2D资深开发者。原文链接:http://sealedabstract.com/rants/why-mobile-web-apps-are-slow/

0
0

近期活动

更多

2015中国大数据技术大会

为了更好帮助企业深入了解国内外最新大数据技术,掌握更多行业大数据实践经验,进一步推进大数据技术创新、行业应用和人才培养,2015年12月10-12日,由中国计算机学会(CCF)主办,CCF大数据专家委员会承办,中国科学院计算技术研究所、北京中科天玑科技有限公司及CSDN共同协办的2015中国大数据技术大会(Big Data Technology Conference 2015,BDTC 2015)将在北京新云南皇冠假日酒店隆重举办。

微博关注

程序员移动端订阅下载

相关热门文章