做者:董宏平(hiyuki),滴滴出行小程序負責人,mpx框架負責人及核心做者
隨着小程序在商業上的巨大成功,小程序開發在國內前端領域愈來愈受到重視,爲了方便廣大開發者更好地進行小程序開發,各種小程序框架也層出不窮,呈現出百花齊放的態勢。可是到目前爲止,業內一直沒有出現一份全面、詳細、客觀、公正的小程序框架測評報告,爲小程序開發者在技術選型時提供參考。因而我便籌劃推出一系列文章,對業內流行的小程序框架進行一次全方位的、客觀公正的測評,本文是系列文章的第一篇——運行時性能篇。html
在本文中,咱們會對下列框架進行運行時性能測試(排名不分前後):前端
其中對於kbone和taro next均以vue做爲業務框架進行測試。vue
運行時性能的測試內容包括如下幾個維度:webpack
框架性能測試demo所有存放於https://github.com/hiyuki/mp-... 中,歡迎廣大開發者進行驗證糾錯及補全;git
爲了使測試結果然實有效,我基於常見的業務場景構建了兩種測試場景,分別是動態測試場景和靜態測試場景。github
動態測試中,視圖基於數據動態渲染,靜態節點較少,視圖更新耗時和setData調用狀況是該測試場景中的主要測試點。web
動態測試demo模擬了實際業務中常見的長列表+多tab場景,該demo中存在兩份優惠券列表數據,一份爲可用券數據,另外一份爲不可用券數據,其中同一時刻視圖中只會渲染展現其中一份數據,能夠在上方的操做區模擬對列表數據的各類操做及視圖展現切換(切tab)。小程序
動態測試demo微信小程序
在動態測試中,我在外部經過函數代理的方式在初始化以前將App、Page和Component構造器進行代理,經過mixin的方式在Page的onLoad和Component的created鉤子中注入setData攔截邏輯,對全部頁面和組件的setData調用進行監聽,並統計小程序的視圖更新耗時及setData調用狀況。該測試方式可以作到對框架代碼的零侵入,可以跟蹤到小程序全量的setData行爲並進行獨立的耗時計算,具備很強的普適性,代碼具體實現能夠查看https://github.com/hiyuki/mp-...api
靜態測試模擬業務中靜態頁面的場景,如運營活動和文章等頁面,頁面內具有大量的靜態節點,而沒有數據動態渲染,初始ready耗時是該場景下測試的重心。
靜態測試demo使用了我去年發表的一篇技術文章的html代碼進行小程序適配構建,其中包含大量靜態節點及文本內容。
靜態測試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
結論分析:
咱們使用刷新頁面
操做觸發頁面從新加載,對於大部分框架來講,頁面渲染耗時是從觸發刷新操做到頁面執行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
結論分析:
這裏後臺數據的定義爲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
結論分析:
刷新頁面後咱們使用新增不可用券(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
結論分析:
因爲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
結論分析:
咱們在可用券數量爲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
結論分析:
咱們將proxySetData
的count和size選項設置爲true,開啓setData的次數和體積統計,從新構建後按照如下流程執行系列操做,並統計setData的調用次數和發送數據的體積。
操做流程以下:
操做完成後咱們使用getCount
和getSize
方法獲取累積的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
結論分析:
此處的頁面渲染耗時與前面描述的動態測試場景中相同,測試結果以下:
頁面渲染耗時 | |
---|---|
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
結論分析:
綜合上述測試數據,咱們獲得最終的小程序框架運行時性能排名爲:
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進行開發,並利用框架提供的跨端能力對微信和支付寶入口進行同步業務迭代,大大提高了業務開發效率。