小程序框架運行時性能大測評

做者:董宏平(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進行開發,並利用框架提供的跨端能力對微信和支付寶入口進行同步業務迭代,大大提高了業務開發效率。

相關文章
相關標籤/搜索