【性能優化實戰】百度小程序FMP優化實錄
發表時(shí)間:2021-1-6
發布人(rén):融晨科技
浏覽次數:102
背景
小程序從首次發布至今,經過了(le/liǎo)幾十個(gè)版本的(de)叠代。随着業務發展,頁面功能内容的(de)不(bù)斷增多,相關性能數據不(bù)斷變差,核心性能數據 FMP 長期處在(zài) 2000ms 以(yǐ)上(shàng)。
在(zài)該項目之(zhī)前,我們團隊也(yě)對小程序做了(le/liǎo)一定的(de)性能調優工作,内容包括:
- 包體積優化,去除了(le/liǎo)不(bù)少引用在(zài)項目中的(de)圖片素材文件,将包體積優化至 500kb 以(yǐ)下;
- 聯合後端對耗時(shí)較高的(de)業務接口做優化,單個(gè)接口返回速度需要(yào / yāo)控制在(zài) 100ms 左右;
- 優化了(le/liǎo)部分業務邏輯,小程序啓動時(shí)減少了(le/liǎo)一些不(bù)必要(yào / yāo)的(de)操作邏輯;
- 使用了(le/liǎo)小程序框架提供的(de)最新生命周期 onInit ,可提前 100ms 左右發起業務網絡請求;
- 使用 prelink 預連接網絡,提升數據接口的(de)請求效率。
經過上(shàng)述手段之(zhī)後,FMP 降到(dào)了(le/liǎo) 1900ms 左右,後續再也(yě)無法産生優化效果。
以(yǐ)上(shàng)優化手段,基本排除了(le/liǎo)網絡連接,包體積優化不(bù)到(dào)位引起的(de)性能不(bù)佳。那麽我們就(jiù)隻有一個(gè)問題需要(yào / yāo)仔細排查 —— 内容的(de)渲染效率。
問題發現
目前從小程序的(de)最大(dà)入口頁面爲(wéi / wèi)問答頁,整體 pv 占比超過 6 成,那麽我們優先優化這(zhè)個(gè)頁面,便可以(yǐ)帶來(lái)性能收益的(de)最大(dà)化。
通讀問答頁代碼,按顯示順序從上(shàng)到(dào)下,整個(gè)頁面的(de)功能點依次爲(wéi / wèi):
- 直播信息橫條
- 問題區
- 回答區
- 廣告組件區
- 爲(wéi / wèi)你推薦 feedlist
需要(yào / yāo)展現的(de)内容類别很多,内容信息量較爲(wéi / wèi)龐大(dà)。部分内容需要(yào / yāo)單獨接口獲取,外加上(shàng)引入的(de)廣告組件,展現效率完全無法優化。
因爲(wéi / wèi)以(yǐ)上(shàng)業務内容的(de)展現需要(yào / yāo),在(zài)加載時(shí),使用 setData 觸發内容渲染,會造成較大(dà)問題,比如:
- 加載期間調用 setData 的(de)頻次過多,onLoad 時(shí)會 set 、onShow 時(shí)會 set ,不(bù)同階段發起的(de)異步數據加載後也(yě)會 set 。當前線程内同時(shí)的(de)多次 setData ,極易造成小程序渲染線程擁塞,影響内容渲染效率。
- 單次 setData 數據量過多,接口數據返回後,所有頁面内需要(yào / yāo)的(de)數據都一次性被提交到(dào)渲染線程中渲染,導緻線程等待時(shí)間長,影響了(le/liǎo)有效内容的(de)最終展現。雖然減少 setData 調用次數是(shì)官方提倡的(de),但是(shì)單次提交過多數據渲染,也(yě)并不(bù)是(shì)最優的(de)策略。
以(yǐ)上(shàng)兩條 setData 的(de)使用問題,在(zài)配置較好的(de)手機設備上(shàng),并不(bù)會體現出(chū)問題,但是(shì)對于(yú)中低配置的(de)手機設備,因爲(wéi / wèi)操作擁塞或大(dà)量數據渲染操作帶來(lái)的(de)渲染延遲,造成的(de)用戶體驗損失還是(shì)很大(dà)的(de)。
優化前的(de)問答頁數據渲染示意圖
優化之(zhī)前,頁面加載完數據之(zhī)後的(de)首次渲染,會一次提交問題區、回答區、廣告組件區三個(gè)部分的(de)渲染任務,由于(yú)這(zhè)三個(gè)區域涉及的(de)内容量比較大(dà),基本都會超過一屏,甚至兩屏以(yǐ)上(shàng),另外各個(gè)區域也(yě)都包含一些圖文内容,加上(shàng)本身耗時(shí)較高的(de)廣告組件。整體頁面内容渲染速度很差。并且,因爲(wéi / wèi)存在(zài)直播信息橫條等單獨異步請求加載的(de)數據内容渲染,也(yě)容易造成 setData 操作在(zài)小程序渲染線程中擁塞現象的(de)發生。
所以(yǐ),從小程序 FMP 的(de)統計規則來(lái)看,目前的(de)數據渲染邏輯,顯然并不(bù)是(shì)最優的(de)。
既然 FMP 主要(yào / yāo)統計的(de)是(shì)用戶第一眼可以(yǐ)看到(dào)的(de)首屏位置内容,那麽我們是(shì)不(bù)是(shì)可以(yǐ)換個(gè)思路來(lái)完成我們的(de)内容渲染工作。
在(zài)确保數據接口性能已經符合常規标準的(de)情況下,我們可以(yǐ)使用更聰明的(de)渲染策略。
優化方案
爲(wéi / wèi)了(le/liǎo)解決上(shàng)述問題,我們構思了(le/liǎo)一套分屏式内容渲染策略,意在(zài)讓用戶能最快速度的(de)先看到(dào)一部分關鍵内容,再分階段渲染剩下需要(yào / yāo)被渲染的(de)數據,而(ér)那些不(bù)需要(yào / yāo)被自動渲染的(de)數據,可以(yǐ)改成由用戶某種行爲(wéi / wèi)(比如滑動頁面)觸發加載和(hé / huò)渲染。
優化後的(de)問答頁渲染示意圖
PS:廣告組件本身爲(wéi / wèi)異步組件,第二次 setData 會觸發廣告組件渲染,而(ér)廣告組件内部自行發起異步内容的(de)加載。
優化後的(de)問答頁渲染邏輯,整體上(shàng)被拆分爲(wéi / wèi)四個(gè)階段:
- 核心内容快速渲染階段。該階段爲(wéi / wèi) FMP 主要(yào / yāo)檢測的(de)數據渲染時(shí)長,所以(yǐ)在(zài)這(zhè)個(gè)階段,我們需要(yào / yāo)讓頁面的(de)内容和(hé / huò)元素,足夠裝滿一屏。
- 核心内容補全渲染階段。該階段将核心内容中存在(zài)的(de)耗時(shí)内容,比如圖片、視頻以(yǐ)及小程序 native 組件等内容渲染上(shàng)屏(注:關于(yú)渲染比較耗時(shí)的(de)組件,目前已知視頻 video 、所有小程序 native 組件,都不(bù)适宜放在(zài)第一階段直接渲染,圖片 image 如果條件允許,也(yě)盡量不(bù)放在(zài)第一屏)。
- 後續内容渲染階段。該階段将本次接口返回的(de)需要(yào / yāo)渲染的(de)數據全部上(shàng)屏。
- 其他(tā)非主要(yào / yāo)異步數據渲染階段(圖例中的(de)直播信息橫條)。将另外一個(gè)接口的(de)數據渲染上(shàng)屏。
PS:如果存在(zài)核心内容渲染完成後依舊無法撐滿一屏的(de)情況,可以(yǐ)考慮設置整體頁面 min-height:100vh ,或者頁面下方放置占位元素,來(lái)達到(dào)撐滿一屏的(de)效果。
優化成果
該優化版于(yú)2020年8月4日上(shàng)午11點左右全量上(shàng)線,在(zài)手百中逐步放量。 FMP 指标在(zài)8月5日和(hé / huò)6日兩天快速下降,7日逐步穩定。總計優化 FMP 指标 540ms 。
從數據表現來(lái)看,優化效果非常明顯。
并且,問答頁作爲(wéi / wèi)小程序 pv 最大(dà)的(de)落地(dì / de)頁,占據總 pv 的(de) 60% 左右,另外還有 40% 的(de)其他(tā)頁面需要(yào / yāo)我們持續優化,未來(lái)數據表現還有不(bù)小的(de)優化空間。
工具建設
工欲善其事必先利其器。後續我們還需要(yào / yāo)優化其他(tā)入口頁面的(de)性能,以(yǐ)及爲(wéi / wèi)後續開發高性能頁面做持續的(de)技術儲備,所以(yǐ)我們将開發中遇到(dào)的(de)和(hé / huò)性能有關的(de)問題做了(le/liǎo)一些抽象,通過打造基礎操作的(de)工具類庫,從底層上(shàng)來(lái)解決或者規避問題。
上(shàng)文中有提到(dào),同時(shí)發起多個(gè) setData 操作,極易造成小程序渲染線程的(de)擁塞,導緻渲染效率受到(dào)影響,降低小程序内容上(shàng)屏的(de)效率。實際開發中,我們如果要(yào / yāo)避免同時(shí)發起多個(gè) setData ,必然會帶來(lái)額外的(de)邏輯思考成本和(hé / huò)代碼結構調整的(de)成本,也(yě)容易因爲(wéi / wèi)調整,降低代碼的(de)可讀性和(hé / huò)可維護性。爲(wéi / wèi)了(le/liǎo)兼顧渲染性能的(de)需要(yào / yāo)和(hé / huò)代碼結構的(de)可讀性,以(yǐ)及代碼觀感,我們專門設計了(le/liǎo)一個(gè)内容渲染任務管理器。
DataSetter
DataSetter 目前已經集成在(zài)團隊内部的(de)小程序工程腳手架中,通過 AdvancedPage 創建的(de)小程序 Page 實例,即可支持通過該管理器開放的(de) api 接口,向小程序的(de)渲染線程提交數據渲染任務。
DataSetter 将小程序 setData 操作封裝爲(wéi / wèi)一個(gè)隊列式的(de)渲染任務管理器,使用 DataSetter 進行 set 數據操作,可以(yǐ)使得單位時(shí)間内隻有一個(gè) setData 操作被執行,而(ér)其他(tā)被同時(shí) set 的(de)數據,将在(zài)隊列中排隊依次執行。
圖例:優化前同時(shí) setData ,會導緻小程序渲染線程的(de)擁塞
圖例:優化後同時(shí) set ,DataSetter 會整體管理數據渲染任務,不(bù)會造成渲染線程擁塞
爲(wéi / wèi)了(le/liǎo)支持分屏式渲染策略的(de)編寫,DataSetter 的(de) API 被設計爲(wéi / wèi)鏈式調用式設計。可以(yǐ)以(yǐ)非嵌套的(de)方式編寫N階段内容渲染邏輯,代碼行文清晰易懂。
this.$dataSetter.set({
// 第一階段渲染數據
status:'success',
aaa:111
}).done(e => {
// 第一階段渲染完成
console.log('第一階段渲染完成');
}).set({
// 第二階段渲染數據
bbb:222
}).set({
// 第三階段渲染數據
ccc:333
}).done(e => {
// 第三階段渲染完成
console.log('第三階段渲染完成‘);
});
複制代碼
DataSetter 源碼
/**
* @name DataSetter
* @description setData語法增強,支持鏈式調用和(hé / huò)隊列式set數據,一次setData成功之(zhī)後才開始下一次setData
*/
class DataSetter {
queue = [];
context = null;
index = 0;
constructor(context) {
this.context = context;
}
set(dataset = {}) {
this.queue.push({dataset, callback: null});
if (this.queue.length === 1) {
this.exec();
}
return this;
}
done(callback) {
this.queue[this.queue.length - 1].callback = callback;
return this;
}
exec() {
let task = this.queue[this.index];
if (!task) {
// console.log('all task done!');
this.refresh();
return;
}
const next = () => {
// console.log(set data ${this.index} ok!);
task.callback && task.callback();
this.index++;
this.exec();
};
// 如果當前任務dataset爲(wéi / wèi)空,則不(bù)調用原生setData,直接執行回調
if (!task.dataset || Object.keys(task.dataset).length < 1) {
next();
return;
}
// console.log(set data ${this.index});
this.context.setData(task.dataset, next);
}
refresh() {
this.queue = [];
this.index = 0;
}
}
// Page Demo
Page({
$dataSetter: null,
onLoad() {
this.$dataSetter = new DataSetter(this);
}
});
複制代碼
後記
造成小程序性能不(bù)理想的(de)情況有很多,而(ér)渲染問題的(de)解決和(hé / huò)優化是(shì)可以(yǐ)帶來(lái)最大(dà)收益的(de),并且如果能根據實際的(de)業務場景,來(lái)靈活設計視圖的(de)渲染策略,往往可以(yǐ)帶來(lái)奇效。渲染問題優化是(shì)一件非常精細的(de)事情,尤其是(shì)面對逐漸複雜的(de)業務代碼,敢于(yú)去改造嘗試,才是(shì)最終成功的(de)起點。
文章轉載自:https://segmentfault.com/a/1190000023754391
作者:百度小程序技術