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