Tagged: JavaScript Toggle Comment Threads | 键盘快捷键

  • Roger 9:37 am on September 2, 2013 固定链接 | 回复
    Tags: , , , , JavaScript, uc   

    High Performance Canvas Game for Android 

    UC在今年5月份发布了支持硬件加速的实验室版,开始实验性地支持对2D Canvas进行硬件加速。在9月份发布的9.3版本中开始分开两个版本 – 普通版和加速版,针对中高端手机的加速版开始正式支持硬件加速2D Canvas渲染。并且在未来将要发布的9.4加速版还会带来全新的Canvas渲染架构,进一步提升性能和减少对系统资源的占用。
    这篇文章的主要目的是为移动Canvas游戏的开发者如何针对Android UC浏览器9.4加速版进行渲染性能优化提供指导,不过文中的大部分内容也适用于Android平台其它支持硬件加速2D Canvas的浏览器,比如Chrome for Android。另外,这篇文章的内容主要是针对渲染性能优化,而不是JavaScript性能优化,不过因为Android UC浏览器使用的是V8引擎,所以您应该很容易找到很多如何针对V8优化JavaScript性能的文章。

    Rule #0 为移动平台进行优化

    为移动平台进行优化是十分重要的,因为移动平台的性能大概只有桌面平台的1/10左右(*1),它通常意味着:
    1. 更慢的CPU速度,这意味着不经过优化的JavaScript代码,性能会十分糟糕;
    2. 更小的内存,没有虚拟内存的支持,这意味着加载太多的资源容易导致内存不足,JSVM更容易引发GC,并且GC造成的停顿时间也越长;
    3. 更慢的GPU速度,没有独立的显存,内存带宽相比PC要慢的多,这意味着即使使用GPU对Canvas进行加速,您仍然需要小心网页DOM树的复杂度,游戏所使用的分辨率(Canvas的大小),图片资源的分辨率,游戏场景的复杂度和尽量避免Overdraw等等;
    注释:
    1. 如果您需要对移动平台浏览器的性能有一个全面的了解,建议阅读文章“Why mobile web apps are slow”(原文译文)和”5 Myths About Mobile Web Performance“(原文译文)。

    Rule #1 为Android而不是iOS而优化

    牢记这一点非常重要,Mobile Safari的Canvas渲染机制跟Android平台有很大的不同,特别地针对Mobile Safari进行优化的Canvas游戏在Android平台的渲染性能会十分的糟糕。
    Mobile Safari使用了iOS/MacOS平台特有的IOSurface作为Canvas的Buffer,当通过Canvas API往IOSurface绘制内容的时候是没有GPU加速的,iOS仍然使用CPU进行绘制,但是将一个IOSurface绘制到另外一个IOSurface上的时候,iOS会使用GPU的2D位拷贝加速单元进行加速(*1)。这种机制其实也是iOS UI界面Layer Rendering渲染架构的基础。所以为iOS优化的Canvas游戏会倾向于使用大量的Off-Screen Canvas(*2),不管是静态的图片也好,还是需要动态产生的内容也好,统统都缓存到一个Off-Screen Canvas上,最终游戏场景的绘制就是一个把一堆Off-Screen Canvas绘制到一个On-Screen Canvas的过程,这样就可以充分利用iOS绘制IOSurface到IOSurface使用了GPU加速的特性来提升渲染性能。
    但是这种大量使用Off-Screen Canvas的做法在Android平台的浏览器上会非常糟糕。Android平台并没有IOSurface的同等物(一块同时支持CPU读写和GPU读写的缓冲区),所以它只能使用GL API对Canvas进行加速,一个加速的Canvas,它的Buffer是一个GL Texture(被attach到一个FBO上),这意味着:
    1. 无论是绘制到Canvas本身,还是Canvas绘制到Canvas都是GPU加速的,普通的位图要绘制到Canvas上,需要先被加载到一个Texture中;
    2. Texture Buffer只能通过GPU进行读写,如果要使用CPU访问,必须先通过glReadPixels把内容从显存拷贝到一块普通内存,而这个操作会非常慢并且会造成GL渲染流水线的阻塞;
    3. 如果游戏频繁创建和销毁一些比较小的Canvas,会很容易造成显存的碎片化,让显存的耗尽速度加快,并且创建太多的Canvas也容易把GPU资源都消耗光,导致后续的分配失败和渲染错误;
    4. 当每个Game Loop都对多个Canvas进行同时更新时,会导致GL Context不断地切换不同的Render Target(FBO),而这对GL的渲染性能有很大的影响;
    后续的内容会进一步说明如何针对使用GL加速的Canvas渲染架构进行优化。
    注释:
    1. 一般GPU都会带有多个独立的加速单元,包括3D加速单元,支持GL和D3D这样的3D API;2D位拷贝加速单元,对将一块缓冲区绘制到另外一块缓冲区进行加速;2D矢量绘制加速单元,支持像OpenVG这样的2D API,但是Android平台只支持通过GL API使用GPU加速,并没有公开的2D位拷贝加速API,虽然2.x的时候厂商可以提供一个copybit模块对位拷贝进行加速,供SurfaceFlinger使用,但这个模块不是通用的,并且不对外公开,另外在4.x的时候也已经移除了。
    2. Off-Screen Canvas在文中是指display:none,没有attach到DOM树上的Canvas,相对于On-Screen Canvas而言。

    Rule #2 优化网页的DOM树结构

     

    Canvas只是网页的一部分,它最终要显示出来还需要浏览器对网页本身的绘制,如果网页的DOM树结构越复杂,浏览器花在网页绘制上的时间也就越长,网页绘制占用的CPU/GPU资源也就越多,而留给Canvas绘制的CPU/GPU资源也就越少,这意味着Canvas绘制本身需要的时间也越长。并且网页绘制的耗时越长,Canvas最终更新到屏幕上的延迟也就越长,总之,这是一个此消彼涨的过程。所以,优化网页的DOM树结构,让其尽可能简单,浏览器就可以把更多的系统资源花费在Canvas的绘制上,从而提升Canvas的渲染性能。

     

    最理想的DOM结构就是只包含一个<body>,加上一个<div>作为容器和加上一个<canvas>本身。如果Canvas上面需要显示其它的网页内容,最好只是用于一些临时使用的对话框之类的东西,而不是一直固定显示。

     

    Rule #3 优化网页元素的css背景设置

     

    跟#2的道理一样,背景设置越简单或者根本不设置背景,浏览器花费在网页本身绘制的开销也就越小,一般来说<canvas>元素本身都不应该设置css背景,它的背景应该通过Canvas API来绘制,避免浏览器在绘制<canvas>元素时还要先绘制背景,然后再绘制Canvas的内容。另外<body>和其它元素都应该首先考虑使用background-color而不是background-image,因为background-image的绘制耗时比一个纯色填充要大的多,而且背景图片本身还需要消耗额外的显存资源(生成Texture)。

     

     Rule #4 使用合适大小的Canvas

     

    考虑移动设备的性能限制,Canvas不适宜太大,否则需要消耗更多的GPU资源和内存带宽,480p或者600p是一个比较合适的选择(横屏游戏可以选择800p或者960p),一般不应该超过720p。并且游戏图片资源的分辨率应该跟Canvas的分辨率保持一致,也就是正常情况下图片绘制到Canvas上应该不需要缩放。

     

    我们需要避免创建了一个较大的Canvas,但是仍然使用较低分辨率的图片资源,图片绘制到Canvas上还需要经过缩放的情况。这样做毫无意义,因为游戏本身的分辨率是由图片资源的分辨率来决定的,上述的情形既不能提升游戏的精美程度,也白白浪费了系统资源。

     

    如果自己预先指定了Canvas的大小,又希望Canvas在网页中全屏显示,可以通过<meta viewport>标签设置viewport的大小(*1,*2),直接告诉浏览器网页虚拟viewport的宽度应该是多少,并且让viewport的宽度等于Canvas的宽度,而浏览器会自动按照viewport宽度和屏幕宽屏的比值对网页进行整体放大。

     

    注释:
    1. <meta viewport>的设置可以参考这个例子:http://www.craftymind.com/factory/guimark3/bitmap/GM3_JS_Bitmap.html
    2. Android系统浏览器在网页不指定viewport宽度时,它会认为这是一个WWW页面,并且使用980的默认viewport宽度,UC浏览器也遵循了同样的做法。这意味着您不设置viewport宽度,并且直接使用window.clientWidth作为Canvas的宽度时,就会创建出一个980p的Canvas,通常这是毫无意义的;

     Rule #5 避免使用多个On-Screen Canvas

    如#1所述,多个Canvas同时更新会降低GL渲染的效率,并且如#2所述,多个On-Screen Canvas会导致网页本身的绘制时间增加,所以应该避免使用。

    Rule #6 合理地使用Off-Screen Canvas

    在GL加速的Canvas渲染架构下,合理地使用Off-Screen Canvas可以在某些特定的场景提升渲染性能,但是不合理的使用反而会导致性能下降。
    1. 将图片绘制到一个Off-Screen Canvas上,然后把这个Canvas当作原图片使用,这种用法,如#1所述,在iOS上是有用的,但是在Android上不必要的,甚至会导致额外的资源浪费(虽然渲染性能还是一样)。浏览器会自动将需要绘制到Canvas的图片加载成Texture并缓存起来,避免反复加载,只要这个缓存池大小没有超过限制,图片的绘制就只需要付出一次贴图的开销,这对GPU来说是很小的;
    2. 避免使用过大或者过小的Off-Screen Canvas —— 首先过大的Canvas会超过系统的Max Texture Size而无法进行加速(*1,*2),而太小的Canvas(*3,*4),因为对它加速不但不会加快渲染速度,反而会导致如#1所述的一些问题 —— 加快GPU资源的耗尽,频繁切换Render Target的额外开销等,所以也是不加速的;
    3. 避免频繁动态创建和销毁Canvas对象,这样很容易引发GC,而且浏览器为了避免大量的Canvas Buffer把GPU资源耗尽,还会在接近临界值时进行强制GC(*5),而强制GC造成的停顿比一般GC还要长,通常会达到500ms~1000ms。所以一般来说应该事先生成所有需要的Canvas然后一直使用,或者建立一个缓存池来回收和重用;
    4. Canvas初始大小设置后就不应该再改变,否则浏览器需要为它分配新的Buffer;
    5. 需要动态生成的内容,可以在一个Off-Screen Canvas上预先生成,然后直接将这个Canvas绘制到On-Screen Canvas上,但是这个生成应该是一次性的(或者偶尔),而不是每个Game Loop都需要更新,否则就会造成#1所述的问题 —— 频繁切换Render Target的额外开销;
    6. 如果场景中的部分内容很少发生变化,但是位置,缩放比例,旋转角度,透明度等属性需要频繁变化,可以把一个Off-Screen Canvas当作Layer使用,缓存这部分内容,然后在绘制这部分内容时就直接绘制这个Off-Screen Canvas;
    总结一下,Off-Screen Canvas的使用应该尽量遵循以下原则:
    1. 数量适中(越少越好);
    2. 大小适中(面积128×128以上,长宽2048以内,并且为2的幂次方对GPU来说是最友好的);
    3. 一次创建,大小固定,持续使用;
    4. 读多写少(可以在每个Game Loop都绘制到On-Screen Canvas上,但是自身更新/变化的次数应该很少,避免每个Game Loop都更新);
    注释:
    1. 一般手机的Max Texture Size是2048,高端的机器可能会到4096或者8192,Canvas长宽任意一边超过这个大小都无法使用Texture做为自己的Buffer;
    2. 非加速的Canvas仍然使用普通的Bitmap作为自己的Buffer,这意味着它的绘制仍然使用CPU,并且它绘制到另外一个Canvas还需要先加载成Texture,而加速的Canvas本身就是一个Texture,所以它绘制到另外一个Canvas上只需要一次贴图的开销;
    3. WebKit默认的设置是128×128大小以内的Canvas不加速,UC和Chrome都使用了默认的设置;
    4. 把一个比较大,加速的Canvas绘制到一个比较小,不加速的Canvas会非常非常慢,这是因为浏览器需要从显存拷贝内容到普通内存,拷贝的速度很慢并且会造成GL渲染流水线的阻塞;
    5. 一个小技巧是,如果一个Canvas不再使用,可以将它的长宽设置为0,这样在JSVM的垃圾收集器还没有回收该Canvas对象时,浏览器就可以先释放它的Buffer,这样可以避免浏览器因为Buffer占用太多而不得不强制GC,不过总的来说最好还是自己建立缓存池;

    Rule #7 避免频繁调用getImageData,putImageData和toDataURL

    因为它们都会需要从显存拷贝内容到普通内存,或者相反,拷贝的速度很慢并且会造成GL渲染流水线的阻塞。所以不要在每个Game Loop都调用这几个API。

    Rule #8 如果需要最大帧率,优先使用requestAnimationFrame而不是Timer

    如果您的游戏只需要20或者30帧,那么就只能使用Timer。但是如果希望达到设备本身的最大帧率,则应该使用rAF,因为rAF可以让浏览器把网页绘制,Canvas绘制跟屏幕刷新保持同步,减少Canvas更新的延迟,并且在网页不可见的时候还可以自动停止rAF回调,避免无谓的浪费电池。

    Rule #9 图片资源大小应该对GPU友好

    1. 避免使用太多小图片,而是应该把它们拼接成一张大图;
    2. 拼接的图片长宽应该是2的幂次方,并且小于或者等于2048,512×512,1024×1024都是不错的选择;
    3. 拼接的图片应该尽量避免留下大量空白区域,造成无谓的浪费;
    Advertisements
     
    • shuyong 8:43 下午 on 四月 13, 2014 固定链接 | 回复

      好文章。以后写的一些文档会参考这里的观点。

      不过这个文章有一个小遗憾。在“Rule #1 为Android而不是iOS而优化”中有一句:
      “Android平台并没有IOSurface的同等物(一块同时支持CPU读写和GPU读写的缓冲区)”

      这里虽然没有错,因为ANDROID平台确实没有提供现成的off-screen nativewindow / nativewindowbuffer。但是应用程序其实是可以自己实现相应的off-screen window / buffer的。这需要思维上有点突破,把自己当成是system engineer,而不只是application engineer。多看一些其它项目,理解interface / implement,就会有新的天地,新的想法了。

    • Roger 10:21 上午 on 四月 14, 2014 固定链接 | 回复

      实际上Android 的 GraphicBuffer 有类似的作用,也可以被应用所使用,但是它有很多限制,不如IOSurface那么灵活,并且不同SoC上的兼容性问题很严重,所以在实际使用中只能用于特定的场景。

      另外不明白这里跟system engineer有何关系,浏览器毕竟不是OS本身,它需要运行在各种不同的硬件/系统版本上,浏览器自身是没办法绕过硬件/系统本身的限制的,除非像Chrome OS/Firefox OS那样。

      • shuyong 11:40 下午 on 四月 14, 2014 固定链接 | 回复

        所谓system engineer,就是有一点可能性,就会尝试实现。Chrome / Firefox也是逐步总结出抽象的移植层,在各个平台上,保证上层都有相同的特性,又要保证可移植性。如果在某个平台没有底层支持,那就实现一个。最终就是要保证上层都有相同的特性。而上层的特性足够多了,足够稳定了,自然而然就想进化到Chrome OS/Firefox OS了。

        我不了解IOSurface,所以对比不了灵活性。但是我做过测试,Android 的 GraphicBuffer应该有足够的稳定性和兼容性,不论是直接调用系统原生的,还是我自己重新实现的。我从Android 2.3到4.3,硬件平台有高通,NVIDIA,FREESCALE,TI,SUMSANG。国产的芯片的系统好像也测过几个版本。GraphicBuffer的接口已经足够稳定,特性也有足够的一致性,否则ANDROID就很难移植和运行了。

        如果自己实现一个off-screen ANativeWindow,通过重载dequeueBuffer / queueBuffer,应该很容易实现CPU / GPU之间访问内存的ZERO-COPY算法的。

        • Roger 9:45 上午 on 四月 15, 2014 固定链接

          如果你了解Chrome,就知道Chrome在Canvas上的实现更保守(或者也可以说是通用)和低效… 所以你既然没有真正了解Chrome的实现,就别在这里乱讲了…

          Android本身对GraphicBuffer的使用比较简单,NativeWindow是Double/Triple Buffering的机制,不存在需要CPU/GPU “同时” 读写同一块Buffer的状况,而Canvas是必须Single Buffer的。如果你测试过Nvidia的芯片,就应该知道Tegra是不支持CPU/GPU “同时” 读写同一块Buffer的,如果一个GraphicBuffer已经通过EGLImage binding到一个Texture上,再试图去unlock它,在Tegra的芯片上就会陷入死锁,高通的Adreno 2xx也有类似问题,Adreno 3xx虽然支持,但是Unlock/Lock一次的开销在3~7ms之间…

  • Roger 9:24 pm on August 12, 2013 固定链接 | 回复
    Tags: , , , , ios, JavaScript,   

    [译文]关于移动Web性能的5个神话 

    译者前言

     

    这篇文章(http://www.sencha.com/blog/5-myths-about-mobile-web-performance/)由Sencha的CEO  Michael Mullany所写,主要是回应早前的一篇引起较多关于移动Web性能讨论的文章“Why mobile web apps are slow”(原文译文),作者的主要观点是“Why mobile web apps are slow”文中给出的数据虽然基本正确,但是对数据的解读却存在误导的成分,并且只考量了JavaScript的性能,而对移动应用来说更关键的Graphics性能并没有被考量在内。并且移动应用性能的提升不仅仅会得益于浏览器提升JavaScript的性能,还会得益于更高程度的GPU加速渲染,多线程并行化处理等等。

     

    译文

     

    我们最近听到了一些在不断重复的关于移动HTML性能的神话,但是这并不完全准确。就像那些流传已久的都市传说一样,它们听起来似乎有理有据,让人信服。但是这些神话是基于不正确的前提,对于原生应用和Web应用的软件堆栈之间的关系的概念是错误的,只是一些因为曲解了数据而推导出的漫无目的的观点。我们认为根据我们多年收集的一些性能数据和对移动Web应用进行性能优化的丰富经验,对这些神话进行澄清是十分重要的。

     

    神话#1:移动Web性能主要是由CPU主导的JavaScript性能所决定的
    真相:大部分Web性能是由浏览器对渲染流水线的优化,DOM操作的速度和使用GPU加速的程度来决定的。更快的JavaScript性能的确不错,但它很少会成为性能的瓶颈。

     

    神话#2:依赖CPU的JavaScript性能仅仅因为硬件的提升才变得更快(aka 摩尔定律)
    真相:在过去4年里面50%以上的JavsScript性能的提升是得益于软件上的优化,而不是硬件上的提升。甚至单线程的JavaScript性能还在不断提升,更不用说现在很多应用开发者通过使用Web Workers来利用多线程提升性能。

     

    神话 #3:移动浏览器最近的性能优化已经基本停滞,未来也没有更多上升的空间
    真相:每一个移动浏览器都在一些特定的领域比起其它浏览器在性能上有10倍-40倍的提高。Surface的SVG性能比iPhone高30倍。而iPhone的DOM操作性能是Surface的10倍。所以对每个浏览器来说,仅仅做到向其它浏览器表现优秀的领域看齐,就有很多性能提升的空间。

     

    神话 #4:移动应用很难再从未来的硬件性能提升中受益
    真相:在过去三年里,每一次硬件更新换代都会带来JavaScript性能的飞跃。不但浏览器的单线程性能仍然在不断提升,并且还会因为更快的GPU速度,更快的内存带宽,和通过多线程并行化处理更充分地利用多核CPU来不断提升性能。越来越多的浏览器已经开始更多地利用并行化来减轻主线程的负担,比如说:Firefox使用了单独的混合线程;Chrome并行化处理HTML解析;还有IE把JavaScript的JIT编译放到了其它线程

     

    神话 #5:JavaScript的垃圾收集对移动应用来说是性能杀手
    真相:这种说法曾经是正确的,不过现在的状况已经不太一样。Chrome在2011年的Chrome 17引入了增量垃圾收集机制Firefox去年也实现了同样的特性。这个特性将每次GC暂停的时间从200ms降到了10ms —— 相当于丢掉一帧 vs 一个可以明显感知的停顿。避免垃圾收集事件的确可以显著提升性能,但是只有你还在使用桌面Web开发模式或者在一个老旧的浏览器上运行,垃圾收集才会成为性能杀手。以我们在Fastbook(一个Facebook HTML5应用的克隆)使用的关键技术为例,通过建立一个DOM节点的对象池来回收不再使用的DOM对象并循环使用,我们成功避免了创建新对象的开销,同时也避免了因为删除旧对象而导致GC。浏览器的确很有可能提供了一个性能非常糟糕的垃圾收集器(比如说旧的IE),但这并不是支持垃圾收集的语言比如JavaScript(或者Java)的天生的无法克服的缺陷。

     

    OK:关键要素

     

    首先,让我们回顾一下最基本的概念。从5万英尺的高度往下看,浏览器本身是构建于操作系统之上的一个丰富和复杂的抽象层。而Web应用混合运用标记语言,JavaScript脚本语言和样式表,使用这个抽象层来创建应用的体验。这个抽象层本身需要额外的性能开销,而开销的多少很大程度决定于你使用抽象层的哪一部分。抽象层的某些部分更快是因为它们离操作系统的系统调用或者某些系统库的调用更近(比如说Canvas2D on MacOS)。另外一些部分会比较慢是因为它们无法直接映射到底层的操作系统,并且它们天生就十分复杂(DOM树操作,JavaScript对象属性访问的原型链遍历)。

     

    很少有移动应用是计算密集型的:没有谁会试图在iPhone上计算DNA序列。大多数应用有着一个合理的响应模型。用户执行一个操作,然后应用会马上回应一个30fps或者更高帧率的视觉动画,然后在几百毫秒之内完成任务。只要应用满足上面的要求,没有人会在乎应用基于的抽象层距离最底层的硅晶圆之间到底有多远。从这点来说,单纯地比较原生应用的抽象层和Web应用的抽象层意义并不大。

     

    对于Sencha来说,我们知道一个优秀的开发者使用移动Web技术和基于一个现代的Web框架比如Sencha Touch,是可以创建出优秀的应用体验的,它运行的足够快,满足用户的期待。并且过去3年的移动性能飞速提升的趋势也使我们深受鼓舞。我们很乐意跟您在接下来的文章里分享一些相关的数据。

     

    但是我们的本意并不是说移动Web应用能够运行的和原生应用一样快,或者它们能够在性能上能够与桌面Web应用相媲美。这不是真实的。移动设备的硬件性能比起桌面设备要慢5倍到10倍:CPU性能比较低,缓存层次结构过于扁平,缺少多级缓存的支持,网络链接的延迟也很高。并且任何软件抽象层本身(比如浏览器)都需要付出额外的开销。其实这不仅仅是Web应用开发者的问题。iOS原生应用的开发者一样可以告诉你,当iOS CoreGraphics在第一款视网膜iPad上性能很低的时候,这使得他们相当一部分人不得不抛弃CoreGraphics而直接使用OpenGL。

     

    进一步追溯神话的真相

     

    通过过去三年持续对Sencha Touch在数据驱动应用中的使用进行性能优化的经历,我们可以很自信的说,我们很少会被最原始的JavaScript性能所困扰。仅仅是在构建Sencha Touch布局系统时,因为Android 2.x的JavaScript性能太差,使得我们改用了Flexbox。

     

    更多时候,我们碰到的问题是DOM操作太慢,浏览器渲染引擎性能比较差和垃圾事件堆积过多无法及时处理。而这些局限基本上都是因为浏览器的架构设计者和开发者造成的,跟JavaScript语言和JavaScript引擎本身并没有本质的联系。举个例子来说,有一次我们和浏览器开发者一起优化浏览器性能,我们最终在某个特定操作上(颜色属性的改变)获得了40倍的性能提高,而这之前是我们的滚动列表实现的性能瓶颈,而类似的例子还有很多。

     

    在iOS和Android上的JavaScript性能

     

    虽然我们说过JavaScript性能其实对于移动设备来说并不是那么重要,但是我们还是希望可以推翻它并没有得到改善的神话。下图是SunSpider测试(越低越好)在iOS上过去4年的得分(按照硬件版本和OS版本划分)。(很幸运,SunSpider是一个广泛被应用的测试,所以我们很容易就在网上找到旧的iOS设备的测试成绩)。2009年的时候,当时运行iOS3的iPhone 3GS得分是15,000 —— 非常的糟糕,跟当时的桌面浏览器的300-600的得分相差30倍左右。

     

     

    然而,如果你把iPhone 3GS升级到iOS 4,5和6,你就可以在同样的硬件上面体验到4倍的性能提升。(在iOS4到iOS5之间性能的巨大的飞跃主要得益于新的Nitro引擎。)实际上SunSpider成绩在iOS7上仍然会有所提高,只是基于保密协议我们这里就不再多说了。跟今天的桌面浏览器相比,最先进的移动浏览器大概还有5倍左右的性能差距 —— 比起09年的30倍已经是相当大的改进。

     

    如果需要了解更多关于iOS硬件和软件性能改进的信息,可以参考Anandtech去年10月份的评测

     

    在Android平台上也差不多有相当等级的改进。从我们的测试实验室,我们找到了一些可以认为是过去3年里面在当时比较有代表性的高性能机器。我们测试了下面4款手机:
    • 三星Captivate Android 2.2(2010年7月发布)
    • Droid Bionic Android 2.3.4(2011年9月发布)
    • 三星Galaxy Note 2 Android 4.1.2(2012年9月发布)
    • 三星Galaxy S4 Android 4.2.2(2013年4月发布)

     

    如下图所示,SunSpider的成绩在过去4年的提升非常明显。从Android 2.x到Android4.x就带来了接近3倍的提升。

     

    无论是iOS还是Android,这些性能提升都不仅仅是由于摩尔定律本身带来的。过去3年,如果按照摩尔定律,我们期望获得的性能提升大概是4倍左右(18个月提升一倍),但实际上却远远不止,所以软件上的优化的确起了相当大的作用。

     

    更多有意义的测试

     

    如之前我们已经提过的,SunSpider的测试成绩其实越来越不重要,因为它跟应用本身的性能要求关系其实并不大。相反,DOM操作的测试,还有Canvas和SVG的测试成绩对应用的用户体验关系更密切。(理想状态下,我们应该还要去测量改变CSS属性的速度,还有CSS动画,过渡动画和几何变换动画的帧率 —— 因为它们在Web应用中使用的更频繁 —— 不过由于缺少浏览器的支持,仍然无法准确地获取这些测试数据)

     

    第一个DOM操作测试:我们使用了Dromaeo Core DOM测试。下图是之前的4台Android设备上得到的测试成绩,我们把Captivates上的4项Core DOM测试成绩(Attributes,Modifications,Query,Traversal)作为1分,其它设备的测试成绩就是相对于Captivates的得分,然后取4项得分的平均值作为最终结果。

     

     

    如你所见,Android从2.x到4.x带来了3.5倍的性能提升,虽然S4比起Note2的提升幅度比较小。我们在iOS设备上也进行了Dromaeo测试。不幸的是,由于iOS无法降级,我们没法得到老的iOS版本的测试成绩,不过我们仍然可以看到随着硬件的升级,Dromaeo测试性能一样是稳步上升。并且有趣的是,不同的iOS6设备之间的Dromaeo性能提升幅度要大于它们的CPU速度提升幅度,这说明了内存带宽或者缓存的速度提升肯定带来了更大的帮助,所以才能比单纯依靠摩尔定律所能获得的结果更好。

     

     

    为了显示浏览器还有多少潜在的性能提升空间(仅仅是为了赶上其它浏览器表现优异的领域),我们还测试了Surface RT。IE槽糕的DOM操作性能一直困扰着我们,但这说明了Surface RT上的IE10在DOM操作上还有很大的改善空间。这也是我们之前打破的一个神话 —— “移动设备的软件堆栈本身已经足够好,未来没有太多的优化空间”。起码对于Windows RT来说,在DOM操作上还有10倍的差距需要去填补。(我们后面还会展现在哪些测试上,iOS表现不佳)

     

    图形性能

     

    除了展现JavaScript和DOM操作性能的巨大提升外,我们还想为您展现浏览器在Canvas和SVG上的性能提升。我们之前就发现了Canvas2D性能在同样硬件上iOS5比iOS4提升了5-8倍。甚至当iPad 2升级到iOS5后,一些局部测试提升了80倍。并且因为Canvas实际上是对iPhone上的CoreGraphics API的直接调用,所以当原生图形性能获得提升时,Canvas性能也获得了同样的提升。在下面的测试中,我们使用了mindcat Canvas2D benchmark来测试性能。这里,我们看到了随着iPhone硬件的提升(都运行iOS6),Canvas性能也在不断提升。

     

    请牢记,上图的显示的数据已经计入了iOS4到iOS5的性能飞跃。如你所见,上图显示出历代iPhone在Canvas2D上的性能提升达到了7倍之多,远比它们的CPU速度提升幅度要大(按照摩尔定律CPU最多也就提升了4倍),这也反映了iPhone的软件堆栈充分利用了CPU/GPU来提升自身的性能。移动Web性能的提升实际上有很大一部分是受益于GPU性能的提升和浏览器更多使用GPU进行图形加速。

     

     

    我们再来看看在Android上运行同样的测试,我们看到一些有趣的数据显示Android上Canvas性能跟CPU性能之间并没有必然的联系。从Android 2.x到Android 4.x上的性能飞跃主要是因为2.x的Canvas完全没有使用GPU加速。这再次说明了,仅仅是浏览器充分利用GPU加速就能够带来巨大的性能提升。

     

    SVG测试

     

    SVG是另外一个可以展现Web性能的广阔的领域。虽然不如Canvas流行(很大程度是因为Canvas已经足够快),SVG的性能随着硬件提升仍然在稳步提升。下图是来自Stephen Bannasch的一个测试,测试了绘制10,000线段构成的一个SVG路径所需的时间。再一次,测试结果显示了各代iPhone CPU/GPU性能提升带来的稳定的SVG性能提升(所有的iPhone都运行iOS6)。

     

     

    但是最大的差距还是源于软件本身:Surface RT比iPhone 5快了30倍(对比iPad4也是如此,虽然这里我们没有列出来)。实际上,Surface RT比起运行在我一年前买的Macbook上的Safari仍然快了10倍!这个差距是是否使用了GPU加速造成的,Window 8/IE10在SVG上充分利用了GPU进行加速,才获得了如此惊人的成果。只要浏览器开发者把更多原来由CPU完成的工作转移到GPU上面去,我们就有可能在iOS和Android上也看到同样的性能提升。

     

    除了上面的长路径绘制外,我们还运行了另外一个来自Cameron Adams的SVG测试,测试了500个不断反弹的小球的动画帧率。再一次,我们可以看到由硬件提升所带来的SVG性能的稳步提升。

     

     

    比起单纯的性能数据,最终的fps值更让人感兴趣。一旦动画超过了30帧,你就可以得到一个跟电影动画(24fps)相似的结果,这样的流畅度已经基本上可以让观看者满意。如果能够达到60帧,那你就会获得由GPU加速带来的极致流畅的体验。

     

    真实世界的性能:垃圾收集,动态语言和更多

     

    我们希望之前的移动性能探索之旅已经说明了一些事情,也打破了一些神话。我们希望为您展现下列真相:
    • JavaScript的性能持续地快速提升
    • 性能的提升由硬件提升和软件优化同时驱动
    • 虽然高性能的JavaScript是一件好事,但实际上大部分Web应用的性能跟JavaScript性能的关系甚少
    • 幸运的是,其它影响Web应用性能的领域,像DOM操作,Canvas,SVG的性能也在飞速提升

     

    虽然我们可以展现一些高速摄影机下的动画测试,不过实际上所有移动Web应用的开发者都清楚,CSS动画,过渡动画和属性修改的性能从Android 2.1开始已经得到极大的提高,并且它们还在不断提高。

     

    之前我们已经澄清了一些不真实的论断,现在再让我们做一个最终的说明。我们不断听到的各种传言汇总而成的最终结论是“移动Web应用总是很慢,这是因为JavaScript是一种低性能的动态语言,并且垃圾收集机制对性能是一个极大的伤害”。应该说这个结论本身并不是完全错误的。不过如果你的Web应用使用类似Sencha Touch这样的框架来动态产生DOM内容,一个很大的优势在于,我们会在浏览器之上,在特定的应用上下文下,合理地去管理对象的创建和销毁,包括事件对象。这样即使你的应用需要展现无穷尽的数据内容(通过表格,列表或者转盘),我们通过回收DOM对象,过滤多余的事件,对要执行的动作进行优先级排序等优化,可以帮助您的应用获得60fps的视觉动画体验。

     

    如果没有一个中间层进行类似的间接处理,的确很容易得到非常糟糕的移动Web应用体验 —— 就像Facebook移动Web应用的第一个版本一样。我们相信如果应用直接使用类似jQuery Mobile这样直接操作底层DOM模型的UI框架时,在可见的未来的确会持续受到性能相关问题的困扰。

     

    汇总

     

    文中包含了大量的数据和覆盖了不同的主题,最后在这里让我们再总结一下。如果您是一位开发者,您应该可以从这篇文章了解到:

    • 移动平台的速度不及桌面平台的1/5 — 较慢的CPU,还有受限的内存大小和速度和较慢的GPU等等。这些都是无法改变的事实。
    • 移动平台的JavaScript + DOM的访问速度越来越快,但是你始终应该把iPhone 5看作跟2008年在桌面电脑上运行的Chrome 1.0一样 (即比桌面版的IE8快5-10倍)。
    • 移动Web应用的图形性能随着浏览器更多使用GPU进行图形加速和其它通用软件优化,已经基本可以实现30帧每秒的动画。
    • 垃圾收集和平台渲染性能有限的问题仍然会使你困扰,所以使用一个类似Sencha Touch这样的抽象框架来获得更佳性能是十分有必要的。
    • 充分利用移动Web平台提供的远程调试器和性能监控能力:像Chrome for Android现在已经提供了一个不错的fps计数器,还可以显示需要混合的图层边界,这可以告诉你哪些网页内容实际上已经生成了贴图并由GPU负责绘制,还有贴图被加载的次数。

     
    我们希望对这些性能数据的回顾能够帮助我们打破一些虚假的神话。我需要感谢在Sencha的所有人对这篇文章的贡献,包括Ariya Hidayat 的审阅和提供了大量关于浏览器性能优化的文章链接,还有 Jacky Nguyen关于Sencha Touch的抽象层如何进行性能优化的一些实现细节。

     

    翻译后记

     

    喔,终于翻译完了,以前还没有翻译过这么长的文章,没想到还真是一件累死人的事情。每一句话都需要斟字酌句细细体会字面下的意思,再用较为通顺的中文表述出来,无论是脑力还是体力都是相当大的摧残,说多了都是泪啊 =_=

     

    应该说要翻译这篇文章,甚至要读懂这篇文章,译者和读者都需要对浏览器内核的一些工作原理有所了解。
    • 比如文中多处强调JavaScript RAW performance和DOM Interaction的区别,这是因为虽然DOM Interaction虽然也是由JS去调用,但是对浏览器来说,实际上JS只是调用浏览器内核提供的JS Binding API,整个Interaction是由浏览器本身去执行的,所以不应该当作JavaScript本身的性能来考量。
    • 又如浏览器所构建的抽象层的不同部分直接或者间接映射到OS的系统调用或者系统库调用对性能的不同影响的说法要怎么理解?举个例子来说,Web应用开发者可以用DOM+CSS或者用Canvas实现同样的动画效果,下面分别是基于QuarkJS实现的两个同样的动画,一个基于DOM,一个基于Canvas,对于Canvas绘制直接使用OS本身的2D绘图库去实现,并且支持GPU加速的浏览器来说,Canvas动画的效率会比使用DOM要高的多,这是因为基于DOM和CSS的动画,浏览器通常需要进行重新计算样式,重新排版,重新光栅化,重新上传贴图,重新混合等这样一个复杂的流程,效率自然高不起来。再举一个例子,对于支持图层混合加速(Accelerated Compositing)和硬件加速的浏览器来说,对付CSS Transform这样的动画就是小菜一碟,因为对它来说这个动画就是不断改变元素所属图层的Transform属性,然后使用GPU重新混合的过程。而支持硬件加速的浏览器所谓的图层混合其实就是通过OpenGL进行贴图的这样一个过程。

     

    最后要说的是,文中的一些观点还是需要在一定的条件下才能成立的,并不是放之四海而皆准,这是读者需要留意的地方:
    • 大部分Web应用性能跟JavaScript性能关系不大,对它的要求不高

     
    是的,大部分是这样的,但不见得你的Web应用就是这大部分之一。实际上,对于有一定复杂程度的基于Canvas的Web Game来说,JavaScript性能很有可能成为它的性能瓶颈。这些Web Game的场景通常比较复杂,包含成百甚至上千的绘图对象(比如实现一个绚丽的粒子效果),需要在JavaScript里面构建一个成百上千个节点的Scene Graph。每绘制一帧,都意味着需要对这个Scene Graph进行遍历,访问每一个节点,更新它的状态,然后再调用Canvas API将它绘制出来。如果要达到30fps的速度,这意味着最多只有30ms左右的时间来完成每一帧(实际上应该没有那么多),即使不算Canvas API本身的绘制开销,单单是遍历和状态更新的操作就很有可能达到几十毫秒的量级了,特别是状态更新中包含大量的碰撞检测和物理运动计算的时候。

    • 通过并行化处理是未来浏览器有效提升性能的一个有效手段

     
    应该说,当前通过并行化处理充分利用多核CPU/GPU提升性能是浏览器内核技术研究发展的一个热点。但是并行化并不是银弹,指望它能够短期内戏剧性地大幅度提升浏览器的整体性能并不现实。

    1. 首先对于移动设备来说,iOS还好,但是Android由于自身的开放性,硬件水平参差不齐,低端硬件还有相当大的保有量,它们缺乏足够的资源去支持并行化,并行化对它们来说反而更糟糕。不过得益于像MTK这样的芯片厂商大力提升中低端设备的性能,现在的千元机性能已经跟几年前不可同日而言,大概再过多一两年这个问题应该就不再成为问题了。
    2. 其次并行化处理并不是想象中的那么容易,因为浏览器的大部分作业实际上都有某种程度的顺序依赖和上下文依赖,需要很多额外的处理才有可能实现部分并行化(毕竟不是数据处理,要做到完全并行化可能性极低)。这样的并行化需要额外的开销,并且只适应于部分场景,有一定的局限性。目前浏览器除了网络链接的部分,并行化程度最高的应该就是渲染了,除了图层混合会运行在独立线程并且主要使用GPU外,像Chrome,Android Browser都把光栅化从主线程剥出来,渲染性能的确从并行化中获益极大,不过也付出了额外的CPU/内存开销的代价。其它领域的并行化进展还是很慢,并且也难见有可能使得性能大幅度提升,比如Chrome在做的HTML解析和样式计算的并行化,最多也就能够减少网页从开始加载到第一次完整呈现的时间,对于整体性能提升意义不大。至于JavaScript,除了IE所实现的JIT并行化外,垃圾收集也是一个有可能剥离出主线程的领域,只是我个人对JavaScript引擎了解不多,不知道具体的技术难点在哪里。
    3. 最后还需要前端开发者有意识地去使用并行化,或者为了更好地支持浏览器的并发作业对自己的应用进行专门优化,比如说Web Workers可以让部分JavaScript代码运行在独立的线程,但在实际的网页里面使用的应该很少。

     

    最后的话

     

    作为读者,如果您能够一直看到这里,说明您应该对Web App/Game开发是有着真爱的^_^ ,所以不妨再看完这最后一节。从我个人的开发经验来看,一个经过充分优化的应用比起没有经过优化的应用通常会有非常明显的性能差别,如果您的Web App/Game对性能要求很高,并且主要运行在移动平台,那么性能优化对您来说那就更加重要了(移动平台可没有那么多可以挥霍性能的空间)。而为了帮助前端开发者更好地做好性能优化,Chrome提供了可称为逆天的神器Dev Tools,学会使用这套工具(推荐Code School上面的视频教程),然后使用它来对您的应用进行性能分析和优化,您会发现这才是真正能够获得戏剧性的性能飞跃的最大可能,这也是所谓的“求诸人不如求诸己”。

     
  • Roger 4:41 pm on October 28, 2011 固定链接 | 回复
    Tags: , Hybiard App, JavaScript, WebDriver, WebView   

    Android WebView 使用loadUrl方法执行本地JavaScript 

    因为Android的WebView类没有像Qt里面的QWebFrame,有一个类似evaluateJavaScript的方法,所以一直以来我都以为Android没办法像Qt一样,可以在加载远程网页之后再注入本地的JavaScript在当前网页内部的JavaScript虚拟机中运行。而注入本地JavaScript的能力对编写一些Web&Native Hybird App还是很有帮助的。

     

    刚好今天看了Android Developer的一篇文章:Introducing Android WebDriver,介绍了WebDriver的使用,发现WebDriver可以在某种程度上在Java端操作页面内的DOM树,所以很好奇它实现的机制。跟踪了一下源码发现,WebDriver是通过loadUrl方法将一段JavaScript注入已经加载的页面内执行,Url使用javascript schema,就是类似javascript:…code…这样的语句,再通过WebView对在JavaScript虚拟机内访问绑定的Java对象的支持,把DOM树的数据暴露给外部的Java代码,达成Java和JavaScript之间的互相通信。

     

    既然WebView提供了这样的支持,在Android平台上基于WebView编写Hybird App就更容易了。

     
    • 红心地瓜 10:22 上午 on 二月 24, 2012 固定链接 | 回复

      qt里面evaluteJavaScript是在页面加载还没开始排版的时候执行注入脚本的,android里面要怎么找到这个点呢?我看WebDriver的介绍里面是自己去下页面的,如果是用WebView加载页面,那在那里执行注入脚本,onPageFinished?好像没有一个比较合理的点,让应用介入。而且重load会加载到历史记录里面吧,跟qt的evaluateJavaScript还是不大一样

      • Roger 10:33 上午 on 二月 24, 2012 固定链接 | 回复

        WebView loadUrl方法执行本地JS是在页面加载结束后,Qt的其实也一样,QWebElement,QWebFrame一般也是加载页面结束后,加入本地对象到JS环境中,再运行本地JS脚本,构成hybird形式的App。

    • Fish 5:01 下午 on 十一月 5, 2012 固定链接 | 回复

      在Webview添加一个WebviewChromeClient对象,在WebviewChromeClient类里面的OnTitleReceive()的时候调用本地js,时机刚刚好。

    • Daniel 2:28 下午 on 四月 4, 2014 固定链接 | 回复

      I have two questions:
      1.Is there a chance to inject javcascript after all page including all resources are loaded?onPageFinished is not exact point.
      2.I found that when the content was changed by injection will rolled back after some navigation and come back.It looks the webview load the original content ,how to make our update cached and let webview loaded from cache?

      Appreciate for any ideas.

  • Roger 10:40 am on October 24, 2011 固定链接 | 回复
    Tags: Hybiard, JavaScript,   

    为什么学习JavaScript 

    最近花了不少时间去学习JavaScript,以一般的理解,JS主要用于浏览器前端编程,也就是所谓的Web App。我自己目前是以本地客户端应用开发为主,也就是所谓的Native App,所以JS跟目前的工作交集不大,不过个人认为JS的学习和研究仍然是一件非常有意义的事情,下面是列举的一些理由。

     

    JavaScript的编程范式跟主流语言有很大的不同,跟Python这样的脚本语言也有比较大的差异,它从其它语言里面吸收了很多不同的概念并糅合在一起。学习JavaScript,可以帮助编程经验比较局限在C++/Java这样的主流语言的我拓宽思维的边界,吸收更多新的理念,及打破以往对编程和编程语言理解的一些局限性。

     

    JavaScript虽然是从浏览器前端编程中发展起来,目前也还是它最大的应用领域。但JS本身仍然是一门完整的面向对象的编程语言,只要能够为它提供一个合适的运行时环境,它就可以很好的应用于在其它的领域。而它作为编程语言的一些优点如下:

    1. JavaScript虚拟机的速度越来越快,以V8虚拟机为代表的即时编译技术的成熟使得构建更复杂的应用成为可能;
    2. JavaScript跟其它语言如C++/Java/.Net的binding越来越容易,使它很适合作为一门粘合剂语言,构建Hybird App;
    3. JavaScript对函数式编程和闭包的支持使得它很适合应用于异步编程,像跟网络相关的应用和以事件驱动为基础的GUI应用;
    4. JavaScript操作复杂的数据结构如树结构很容易,这使得它很适合用于GUI编程和网页编程(后者本来就是它最初的发展目的);

    JavaScript在浏览器前端编程的领域内继续迅猛发展,HTML5标准为浏览器的JS运行时环境增加了大量新的API,提供了更完整的功能,为使用JS编写更复杂和本地化的Web App提供了强有力的支持,而其它以浏览器运行时为核心的中间件如PhoneCap更是增强了通过JS直接访问和控制移动设备的能力,这些都进一步模糊了Web App和Native App的界限。

     

    其它的JS运行时环境的发展也使得JS在除了浏览器前端领域外的应用变得越来越重要,NodeJS构建了JS服务器编程的运行时,QML/JavaScipt构建了JS用于Qt的QML GUI编程的运行时,Windows 8的Windows Runtime构建了JS跟.Net混合编程的运行时等等。所以未来,JS在3个主要的编程领域,本地客户端,浏览器前端,服务器后端都会得到更广泛的应用,特别是本地客户端编程,目前主流的GUI框架和OS的发展都有把JS搭配HTML或者其它的UI脚本语言作为本地GUI应用构建的主要语言的趋势,通过JS跟C++/Java/.Net的Binding,Hybird Native App会变得越来越普遍。

     
c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
回复
e
编辑
o
Show/Hide comments
t
返回顶部
l
Go to login
h
Show/Hide help
shift + esc
取消