作者:董宏平(hiyuki),滴滴出行小程序負責人,mpx框架負責人及核心作者
隨着小程序在商業上的巨大成功,小程序開發在國內前端領域越來越受到重視,爲了方便廣大開發者更好地進行小程序開發,各類小程序框架也層出不窮,呈現出百花齊放的態勢。但是到目前爲止,業內一直沒有出現一份全面、詳細、客觀、公正的小程序框架測評報告,爲小程序開發者在技術選型時提供參考。於是我便籌劃推出一系列文章,對業內流行的小程序框架進行一次全方位的、客觀公正的測評,本文是系列文章的第一篇——運行時性能篇。
在本文中,我們會對下列框架進行運行時性能測試(排名不分先後):
wepy2(https://github.com/Tencent/wepy) @2.0.0-alpha.20
uniapp(https://github.com/dcloudio/u...) @2.0.0-26120200226001
mpx(https://github.com/didi/mpx) @2.5.3
chameleon(https://github.com/didi/chame...) @1.0.5
mpvue(https://github.com/Meituan-Di...) @2.0.6
kbone(https://github.com/Tencent/kb...) @0.8.3
taro next(https://github.com/NervJS/taro) @3.0.0-alpha.5
其中對於kbone和taro next均以vue作爲業務框架進行測試。
運行時性能的測試內容包括以下幾個維度:
框架運行時體積
頁面渲染耗時
頁面更新耗時
局部更新耗時
setData調用次數
setData發送數據大小
框架性能測試demo全部存放於https://github.com/hiyuki/mp-... 中,歡迎廣大開發者進行驗證糾錯及補全;
測試方案
爲了使測試結果真實有效,我基於常見的業務場景構建了兩種測試場景,分別是動態測試場景和靜態測試場景。
動態測試場景
動態測試中,視圖基於數據動態渲染,靜態節點較少,視圖更新耗時和setData調用情況是該測試場景中的主要測試點。
動態測試demo模擬了實際業務中常見的長列表+多tab場景,該demo中存在兩份優惠券列表數據,一份爲可用券數據,另一份爲不可用券數據,其中同一時刻視圖中只會渲染展示其中一份數據,可以在上方的操作區模擬對列表數據的各種操作及視圖展示切換(切tab)。
動態測試demo
在動態測試中,我在外部通過函數代理的方式在初始化之前將App、Page和Component構造器進行代理,通過mixin的方式在Page的onLoad和Component的created鉤子中注入setData攔截邏輯,對所有頁面和組件的setData調用進行監聽,並統計小程序的視圖更新耗時及setData調用情況。該測試方式能夠做到對框架代碼的零侵入,能夠跟蹤到小程序全量的setData行爲並進行獨立的耗時計算,具有很強的普適性,代碼具體實現可以查看https://github.com/hiyuki/mp-...
靜態測試場景
靜態測試模擬業務中靜態頁面的場景,如運營活動和文章等頁面,頁面內具備大量的靜態節點,而沒有數據動態渲染,初始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
結論分析:
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的調用次數和發送數據的體積。
操作流程如下:
100逐級遞增可用券(0->500)
切換至不可用券
新增不可用券(1000)
100逐級遞增可用券(500->1000)
更新可用券(all)
切換至可用券
操作完成後我們使用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
結論分析:
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進行開發,並利用框架提供的跨端能力對微信和支付寶入口進行同步業務迭代,大大提升了業務開發效率。
❤️ 看完三件事 如果你覺得這篇內容對你挺有啓發,我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)
關注我的博客 https://github.com/SHERlocked93/blog,讓我們成爲長期關係
關注公衆號「前端下午茶」,持續爲你推送精選好文,也可以加我爲好友,隨時聊騷。
在看點這裏
作者:董宏平(hiyuki),滴滴出行小程序負責人,mpx框架負責人及核心作者
隨着小程序在商業上的巨大成功,小程序開發在國內前端領域越來越受到重視,爲了方便廣大開發者更好地進行小程序開發,各類小程序框架也層出不窮,呈現出百花齊放的態勢。但是到目前爲止,業內一直沒有出現一份全面、詳細、客觀、公正的小程序框架測評報告,爲小程序開發者在技術選型時提供參考。於是我便籌劃推出一系列文章,對業內流行的小程序框架進行一次全方位的、客觀公正的測評,本文是系列文章的第一篇——運行時性能篇。
在本文中,我們會對下列框架進行運行時性能測試(排名不分先後):
wepy2(https://github.com/Tencent/wepy) @2.0.0-alpha.20
uniapp(https://github.com/dcloudio/u...) @2.0.0-26120200226001
mpx(https://github.com/didi/mpx) @2.5.3
chameleon(https://github.com/didi/chame...) @1.0.5
mpvue(https://github.com/Meituan-Di...) @2.0.6
kbone(https://github.com/Tencent/kb...) @0.8.3
taro next(https://github.com/NervJS/taro) @3.0.0-alpha.5
其中對於kbone和taro next均以vue作爲業務框架進行測試。
運行時性能的測試內容包括以下幾個維度:
框架運行時體積
頁面渲染耗時
頁面更新耗時
局部更新耗時
setData調用次數
setData發送數據大小
框架性能測試demo全部存放於https://github.com/hiyuki/mp-... 中,歡迎廣大開發者進行驗證糾錯及補全;
測試方案
爲了使測試結果真實有效,我基於常見的業務場景構建了兩種測試場景,分別是動態測試場景和靜態測試場景。
動態測試場景
動態測試中,視圖基於數據動態渲染,靜態節點較少,視圖更新耗時和setData調用情況是該測試場景中的主要測試點。
動態測試demo模擬了實際業務中常見的長列表+多tab場景,該demo中存在兩份優惠券列表數據,一份爲可用券數據,另一份爲不可用券數據,其中同一時刻視圖中只會渲染展示其中一份數據,可以在上方的操作區模擬對列表數據的各種操作及視圖展示切換(切tab)。
動態測試demo
在動態測試中,我在外部通過函數代理的方式在初始化之前將App、Page和Component構造器進行代理,通過mixin的方式在Page的onLoad和Component的created鉤子中注入setData攔截邏輯,對所有頁面和組件的setData調用進行監聽,並統計小程序的視圖更新耗時及setData調用情況。該測試方式能夠做到對框架代碼的零侵入,能夠跟蹤到小程序全量的setData行爲並進行獨立的耗時計算,具有很強的普適性,代碼具體實現可以查看https://github.com/hiyuki/mp-...
靜態測試場景
靜態測試模擬業務中靜態頁面的場景,如運營活動和文章等頁面,頁面內具備大量的靜態節點,而沒有數據動態渲染,初始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
結論分析:
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的調用次數和發送數據的體積。
操作流程如下:
100逐級遞增可用券(0->500)
切換至不可用券
新增不可用券(1000)
100逐級遞增可用券(500->1000)
更新可用券(all)
切換至可用券
操作完成後我們使用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
結論分析:
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進行開發,並利用框架提供的跨端能力對微信和支付寶入口進行同步業務迭代,大大提升了業務開發效率。
❤️ 看完三件事
如果你覺得這篇內容對你挺有啓發,我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)
關注我的博客 https://github.com/SHERlocked93/blog,讓我們成爲長期關係
關注公衆號「前端下午茶」,持續爲你推送精選好文,也可以加我爲好友,隨時聊騷。
在看點這裏