小程序框架运行时性能大测评

做者:董宏平(hiyuki),滴滴出行小程序负责人,mpx框架负责人及核心做者

随着小程序在商业上的巨大成功,小程序开发在国内前端领域愈来愈受到重视,为了方便广大开发者更好地进行小程序开发,各种小程序框架也层出不穷,呈现出百花齐放的态势。可是到目前为止,业内一直没有出现一份全面、详细、客观、公正的小程序框架测评报告,为小程序开发者在技术选型时提供参考。因而我便筹划推出一系列文章,对业内流行的小程序框架进行一次全方位的、客观公正的测评,本文是系列文章的第一篇——运行时性能篇。html

在本文中,咱们会对下列框架进行运行时性能测试(排名不分前后):前端

其中对于kbone和taro next均以vue做为业务框架进行测试。vue

运行时性能的测试内容包括如下几个维度:webpack

  • 框架运行时体积
  • 页面渲染耗时
  • 页面更新耗时
  • 局部更新耗时
  • setData调用次数
  • setData发送数据大小

框架性能测试demo所有存放于https://github.com/hiyuki/mp-... 中,欢迎广大开发者进行验证纠错及补全;git

测试方案

为了使测试结果然实有效,我基于常见的业务场景构建了两种测试场景,分别是动态测试场景和静态测试场景。github

动态测试场景

动态测试中,视图基于数据动态渲染,静态节点较少,视图更新耗时和setData调用状况是该测试场景中的主要测试点。web

动态测试demo模拟了实际业务中常见的长列表+多tab场景,该demo中存在两份优惠券列表数据,一份为可用券数据,另外一份为不可用券数据,其中同一时刻视图中只会渲染展现其中一份数据,能够在上方的操做区模拟对列表数据的各类操做及视图展现切换(切tab)。小程序

动态测试demo

动态测试demo微信小程序

在动态测试中,我在外部经过函数代理的方式在初始化以前将App、Page和Component构造器进行代理,经过mixin的方式在Page的onLoad和Component的created钩子中注入setData拦截逻辑,对全部页面和组件的setData调用进行监听,并统计小程序的视图更新耗时及setData调用状况。该测试方式可以作到对框架代码的零侵入,可以跟踪到小程序全量的setData行为并进行独立的耗时计算,具备很强的普适性,代码具体实现能够查看https://github.com/hiyuki/mp-...api

静态测试场景

静态测试模拟业务中静态页面的场景,如运营活动和文章等页面,页面内具有大量的静态节点,而没有数据动态渲染,初始ready耗时是该场景下测试的重心。

静态测试demo使用了我去年发表的一篇技术文章的html代码进行小程序适配构建,其中包含大量静态节点及文本内容。

静态测试demo

静态测试demo

测试流程及数据

如下全部耗时类的测试数据均为微信小程序中真机进行5次测试计算平均值得出,单位均为ms。Ios测试环境为手机型号iPhone 11,系统版本13.3.1,微信版本7.0.12,安卓测试环境为手机型号小米9,系统版本Android10,微信版本7.0.12。
为了使数据展现不过于混乱复杂,文章中所列的数据以Ios的测试结果为主,安卓测试结论与Ios相符,总体耗时比Ios高3~4倍左右,全部的原始测试数据存放在 https://github.com/hiyuki/mp-...
因为transform-runtime引入的core-js会对框架的运行时体积和运行耗时带来必定影响,且不是全部的框架都会在编译时开启transform-runtime,为了对齐测试环境,下述测试均在transform-runtime关闭时进行。

框架运行时体积

因为不是全部框架都可以使用webpack-bundle-analyzer获得精确的包体积占用,这里我经过将各框架生成的demo项目体积减去native编写的demo项目体积做为框架的运行时体积。

demo整体积(KB) 框架运行时体积(KB)
native 27 0
wepy2 66 39
uniapp 114 87
mpx 78 51
chameleon 136 109
mpvue 103 76
kbone 395 368
taro next 183 156

该项测试的结论为:
native > wepy2 > mpx > mpvue > uniapp > chameleon > taro next > kbone

结论分析:

  • wepy2和mpx在框架运行时体积上控制得最好;
  • taro next和kbone因为动态渲染的特性,在dist中会生成递归渲染模板/组件,因此占用体积较大。

页面渲染耗时(动态测试)

咱们使用刷新页面操做触发页面从新加载,对于大部分框架来讲,页面渲染耗时是从触发刷新操做到页面执行onReady的耗时,可是对于像kbone和taro next这样的动态渲染框架,页面执行onReady并不表明视图真正渲染完成,为此,咱们设定了一个特殊规则,在页面onReady触发的1000ms内,在没有任何操做的状况下出现setData回调时,以最后触发的setData回调做为页面渲染完成时机来计算真实的页面渲染耗时,测试结果以下:

页面渲染耗时
native 60.8
wepy2 64
uniapp 56.4
mpx 52.6
chameleon 56.4
mpvue 117.8
kbone 98.6
taro next 89.6
该项测试的耗时并不等同于真实的渲染耗时,因为小程序自身没有提供performance api,真实渲染耗时没法经过js准确测试得出,不过从得出的数据来看该项数据依然具有必定的参考意义。

该项测试的结论为:
mpx ≈ chameleon ≈ uniapp ≈ native ≈ wepy2 > taro next ≈ kbone ≈ mpvue

结论分析:

  • 因为mpvue全量在页面进行渲染,kbone和taro next采用了动态渲染技术,页面渲染耗时较长,其他框架并没有太大区别。

页面更新耗时(无后台数据)

这里后台数据的定义为data中存在但当前页面渲染中未使用到的数据,在这个demo场景下即为不可用券的数据,当前会在不可用券为0的状况下,对可用券列表进行各类操做,并统计更新耗时。

更新耗时的计算方式是从数据操做事件触发开始到对应的setData回调完成的耗时

mpvue中使用了当前时间戳(new Date)做为超时依据对setData进行了超时时间为50ms的节流操做,该方式存在严重问题,当vue内单次渲染同步流程执行耗时超过50ms时,后续组件patch触发的setData会突破这个节流限制,以50ms每次的频率对setData进行高频无效调用。在该性能测试demo中,当优惠券数量超过500时,界面就会彻底卡死。为了顺利跑完整个测试流程,我对该问题进行了简单修复,使用setTimeout重写了节流部分,确保在vue单次渲染流程同步执行完毕后才会调用setData发送合并数据,以后mpvue的全部性能测试都是基于这个patch版原本进行的,该patch版本存放在 https://github.com/hiyuki/mp-...
理论上来说native的性能在进行优化的前提下必定是全部框架的天花板,可是在平常业务开发中咱们可能没法对每一次setData都进行优化,如下性能测试中全部的native数据均采用修改数据后全量发送的形式来实现。

第一项测试咱们使用新增可用券(100)操做将可用券数量由0逐级递增到1000:

100 200 300 400 500 600 700 800 900 1000
native 84.6 69.8 71.6 75 77.2 78.8 82.8 93.2 93.4 105.4
wepy2 118.4 168.6 204.6 246.4 288.6 347.8 389.2 434.2 496 539
uniapp 121.2 100 96 98.2 97.8 99.6 104 102.4 109.4 107.6
mpx 110.4 87.2 82.2 83 80.6 79.6 86.6 90.6 89.2 96.4
chameleon 116.8 115.4 117 119.6 122 125.2 133.8 133.2 144.8 145.6
mpvue 112.8 121.2 140 169 198.8 234.2 278.8 318.4 361.4 408.2
kbone 556.4 762.4 991.6 1220.6 1468.8 1689.6 1933.2 2150.4 2389 2620.6
taro next 470 604.6 759.6 902.4 1056.2 1228 1393.4 1536.2 1707.8 1867.2

而后咱们按顺序逐项点击删除可用券(all) > 新增可用券(1000) > 更新可用券(1) > 更新可用券(all) > 删除可用券(1)

delete(all) add(1000) update(1) update(all) delete(1)
native 32.8 295.6 92.2 92.2 83
wepy2 56.8 726.4 49.2 535 530.8
uniapp 43.6 584.4 54.8 144.8 131.2
mpx 41.8 489.6 52.6 169.4 165.6
chameleon 39 765.6 95.6 237.8 144.8
mpvue 103.6 669.4 404.4 414.8 433.6
kbone 120.2 4978 2356.4 2419.4 2357
taro next 126.6 3930.6 1607.8 1788.6 2318.2
该项测试中初期我update(all)的逻辑是循环对每一个列表项进行更新,形如 listData.forEach((item)=>{item.count++}),发如今chameleon框架中执行界面会彻底卡死,追踪发现chameleon框架中没有对setData进行异步合并处理,而是在数据变更时直接同步发送,这样在数据量为1000的场景下用该方式进行更新会高频触发1000次setData,致使界面卡死;对此,我在chameleon框架的测试demo中,将update(all)的逻辑调整为深clone产生一份更新后的listData,再将其总体赋值到this.listData当中,以确保该项测试可以正常进行。

该项测试的结论为:
native > mpx ≈ uniapp > chameleon > mpvue > wepy2 > taro next > kbone

结论分析:

  • mpx和uniapp在框架内部进行了完善的diff优化,随着数据量的增长,两个框架的新增耗时没有显著上升;
  • wepy2会在数据变动时对props数据也进行setData,在该场景下形成了大量的无效性能损耗,致使性能表现不佳;
  • kbone和taro next采用了动态渲染方案,每次新增更新时会发送大量描述dom结构的数据,与此同时动态递归渲染的耗时也远大于常规的静态模板渲染,使得这两个框架在全部的更新场景下耗时都远大于其余框架。

页面更新耗时(有后台数据)

刷新页面后咱们使用新增不可用券(1000)建立后台数据,观察该操做是否会触发setData并统计耗时

back add(1000)
native 45.2
wepy2 174.6
uniapp 89.4
mpx 0
chameleon 142.6
mpvue 134
kbone 0
taro next 0
mpx进行setData优化时inspired by vue,使用了编译时生成的渲染函数跟踪模板数据依赖,在后台数据变动时不会进行setData调用,而kbone和taro next采用了动态渲染技术模拟了web底层环境,在上层完整地运行了vue框架,也达到了一样的效果。

而后咱们执行和上面无后台数据时相同的操做进行耗时统计,首先是递增100:

100 200 300 400 500 600 700 800 900 1000
native 88 69.8 71.2 80.8 79.4 84.4 89.8 93.2 99.6 108
wepy2 121 173.4 213.6 250 298 345.6 383 434.8 476.8 535.6
uniapp 135.4 112.4 110.6 106.4 109.6 107.2 114.4 116 118.8 117.4
mpx 112.6 86.2 84.6 86.8 90 87.2 91.2 88.8 92.4 93.4
chameleon 178.4 178.2 186.4 184.6 192.6 203.8 210 217.6 232.6 236.8
mpvue 139 151 173.4 194 231.4 258.8 303.4 340.4 384.6 429.4
kbone 559.8 746.6 980.6 1226.8 1450.6 1705.4 1927.2 2154.8 2367.8 2617
taro next 482.6 626.2 755 909.6 1085 1233.2 1384 1568.6 1740.6 1883.8

而后按下表操做顺序逐项点击统计

delete(all) add(1000) update(1) update(all) delete(1)
native 43.4 299.8 89.2 89 87.2
wepy2 43.2 762.4 50 533 522.4
uniapp 57.8 589.8 62.6 160.6 154.4
mpx 45.8 490.8 52.8 167 166
chameleon 93.8 837 184.6 318 220.8
mpvue 124.8 696.2 423.4 419 430.6
kbone 121.4 4978.2 2331.2 2448.4 2348
taro next 129.8 3947.2 1610.4 1813.8 2290.2

该项测试的结论为:
native > mpx > uniapp > chameleon > mpvue > wepy2 > taro next > kbone

结论分析:

  • 具有模板数据跟踪能力的三个框架mpx,kbone和taro next在有后台数据场景下耗时并无显著增长;
  • wepy2当中的diff精度不足,耗时也没有产生明显变化;
  • 其他框架因为每次更新都会对后台数据进行deep diff,耗时都产生了必定提高。

页面更新耗时(大数据量场景)

因为mpvue和taro next的渲染所有在页面中进行,而kbone的渲染方案会额外新增大量的自定义组件,这三个框架都会在优惠券数量达到2000时崩溃白屏,咱们排除了这三个框架对其他框架进行大数据量场景下的页面更新耗时测试

首先仍是在无后台数据场景下使用新增可用券(1000)将可用券数量递增至5000:

1000 2000 3000 4000 5000
native 332.6 350 412.6 498.2 569.4
wepy2 970.2 1531.4 2015.2 2890.6 3364.2
uniapp 655.2 593.4 655 675.6 718.8
mpx 532.2 496 548.6 564 601.8
chameleon 805.4 839.6 952.8 1086.6 1291.8

而后点击新增不可用券(5000)将后台数据量增长至5000,再测试可用券数量递增至5000的耗时:

back add(5000)
native 117.4
wepy2 511.6
uniapp 285
mpx 0
chameleon 824
1000 2000 3000 4000 5000
native 349.8 348.4 430.4 497 594.8
wepy2 1128 1872 2470.4 3263.4 4075.8
uniapp 715 666.8 709.2 755.6 810.2
mpx 538.8 501.8 562.6 573.6 595.2
chameleon 1509.2 1672.4 1951.8 2232.4 2586.2

该项测试的结论为:
native > mpx > uniapp > chameleon > wepy2

结论分析:

  • 在大数据量场景下,框架之间基础性能的差别会变得更加明显,mpx和uniapp依然保持了接近原生的良好性能表现,而chameleon和wepy2则产生了比较显著的性能劣化。

局部更新耗时

咱们在可用券数量为1000的状况下,点击任意一张可用券触发选中状态,以测试局部更新性能

toggleSelect(ms)
native 2
wepy2 2.6
uniapp 2.8
mpx 2.2
chameleon 2
mpvue 289.6
kbone 2440.8
taro next 1975

该项测试的结论为:
native ≈ chameleon ≈ mpx ≈ wepy2 ≈ uniapp > mpvue > taro next > kbone

结论分析:

  • 能够看出全部使用了原生自定义组件进行组件化实现的框架局部更新耗时都极低,这足以证实小程序原生自定义组件的优秀性和重要性;
  • mpvue因为使用了页面更新,局部更新耗时显著增长;
  • kbone和taro next因为递归动态渲染的性能开销巨大,致使局部更新耗时一样巨大。

setData调用

咱们将proxySetData的count和size选项设置为true,开启setData的次数和体积统计,从新构建后按照如下流程执行系列操做,并统计setData的调用次数和发送数据的体积。

操做流程以下:

  1. 100逐级递增可用券(0->500)
  2. 切换至不可用券
  3. 新增不可用券(1000)
  4. 100逐级递增可用券(500->1000)
  5. 更新可用券(all)
  6. 切换至可用券

操做完成后咱们使用getCountgetSize方法获取累积的setData调用次数和数据体积,其中数据体积计算方式为JSON.stringify后按照utf-8编码方式进行体积计算,统计结果为:

count size(KB)
native 14 803
wepy2 3514 1124
mpvue 16 2127
uniapp 14 274
mpx 8 261
chameleon 2515 319
kbone 22 10572
taro next 9 2321

该项测试的结论为:
mpx > uniapp > native > chameleon > wepy2 > taro next > mpvue > kbone

结论分析:

  • mpx框架成功实现了理论上setData的最优;
  • uniapp因为缺失模板追踪能力紧随其后;
  • chameleon因为组件每次建立时都会进行一次没必要要的setData,产生了大量无效setData调用,可是数据的发送自己通过diff,在数据发送量上表现不错;
  • wepy2的组件会在数据更新时调用setData发送已经更新过的props数据,所以也产生了大量无效调用,且diff精度不足,发送的数据量也较大;
  • taro next因为上层彻底基于vue,在数据发送次数上控制到了9次,但因为须要发送大量的dom描述信息,数据发送量较大;
  • mpvue因为使用较长的数据路径描述数据对应的组件,也产生了较大的数据发送量;
  • kbone对于setData的调用控制得不是很好,在上层运行vue的状况依然进行了22次数据发送,且发送的数据量巨大,在此流程中达到了惊人的10MB。

页面渲染耗时(静态测试)

此处的页面渲染耗时与前面描述的动态测试场景中相同,测试结果以下:

页面渲染耗时
native 70.4
wepy2 86.6
mpvue 115.2
uniapp 69.6
mpx 66.6
chameleon 65
kbone 144.2
taro next 119.8

该项测试的结论为:
chameleon ≈ mpx ≈ uniapp ≈ native > wepy2 > mpvue ≈ taro next > kbone

结论分析:

  • 除了kbone和taro next采用动态渲染耗时增长,mpvue使用页面模板渲染性能稍差,其他框架的静态页面渲染表现都和原生差很少。

结论

综合上述测试数据,咱们获得最终的小程序框架运行时性能排名为:
mpx > uniapp > chameleon > wepy2 > mpvue > taro next > kbone

一点私货

虽然kbone和taro next采用了动态渲染技术在性能表现上并不尽如人意,可是我依然认为这是很棒的技术方案。虽然本文从头到位都在进行性能测试和对比,但性能并非框架的所有,开发效率和高可用性仍然是框架的重心,开发效率相信是全部框架设计的初衷,可是高可用性却在很大程度被忽视。从这个角度来讲,kbone和taro next是很是成功的,不一样于过去的转译思路,这种从抹平底层渲染环境的作法可以使上层web框架完整运行,在框架可用性上带来很是大的提高,很是适合于运营类简单小程序的迁移和开发。

我主导开发的mpx框架(https://github.com/didi/mpx) 选择了另外一条道路解决可用性问题,那就是基于小程序原生语法能力进行加强,这样既能避免转译web框架时带来的不肯定性和不稳定性,同时也能带来很是接近于原生的性能表现,对于复杂业务小程序的开发者来讲,很是推荐使用。在跨端输出方面,mpx目前可以完善支持业内所有小程序平台和web平台的同构输出,滴滴内部最重要最复杂的小程序——滴滴出行小程序彻底基于mpx进行开发,并利用框架提供的跨端能力对微信和支付宝入口进行同步业务迭代,大大提高了业务开发效率。

相关文章
相关标签/搜索