在移動(dòng)端的頁(yè)面開(kāi)發(fā)過(guò)程中,我們經(jīng)常提及頁(yè)面性能優(yōu)化、消除頁(yè)面卡頓的話題,如何·確定優(yōu)化策略,我們首先應(yīng)當(dāng)對(duì)頁(yè)面卡頓的行為有所認(rèn)知。
前言
?
頁(yè)面的卡頓現(xiàn)象可以比較明確的分為三個(gè)類型,分別是 “畫面撕裂” 、“丟幀不流暢”、“長(zhǎng)時(shí)間未響應(yīng)”。
“畫面撕裂“ 現(xiàn)象給人直觀的感覺(jué)是頁(yè)面返回內(nèi)容不一致,而造成這種現(xiàn)象的原因在于屏幕的刷新機(jī)制,屏幕的刷新遵循 “Z” 字刷新的方式,因此同一幀的數(shù)據(jù)在上屏?xí)r存在一定的時(shí)間差,當(dāng)幀率大于刷新率時(shí),屏幕對(duì)前一幀的數(shù)據(jù)上屏尚未結(jié)束而后臺(tái)對(duì)后一幀的數(shù)據(jù)處理合成完畢,此時(shí)屏幕完成上屏的數(shù)據(jù)將會(huì)采用后一幀的數(shù)據(jù),造成人眼同一時(shí)刻觀測(cè)的畫面數(shù)據(jù)來(lái)源于不同幀的現(xiàn)象,給人以撕裂感。
?
”畫面丟幀“也被稱為 Jank 現(xiàn)象,給人的直觀感覺(jué)是畫面不流暢,在動(dòng)畫或滾動(dòng)時(shí)出現(xiàn)短暫停滯,而造成丟幀的原因在于屏幕每隔 16ms 發(fā)出一個(gè) VSync 信號(hào),在正常幀的流程中,CPU 接收到 VSync 信號(hào)后會(huì)先通過(guò)交換指針的方式進(jìn)行前后緩沖區(qū)的數(shù)據(jù)同步(該過(guò)程時(shí)耗忽略不計(jì))然后開(kāi)始新一幀數(shù)據(jù)合成,當(dāng)幀率小于刷新率時(shí),下一個(gè) VSync 信號(hào)到來(lái)時(shí)可用于交換的幀數(shù)據(jù)還沒(méi)有通過(guò) CPU 計(jì)算合成,此時(shí)前后緩沖區(qū)的數(shù)據(jù)不會(huì)發(fā)生交換,因此屏幕內(nèi)出現(xiàn)了連續(xù)兩幀使用同一幀數(shù)據(jù)渲染,給人以停頓和不流暢的感覺(jué)。
?
"長(zhǎng)時(shí)間未響應(yīng)"指的是畫面長(zhǎng)時(shí)間等待,等待網(wǎng)絡(luò)請(qǐng)求或其他事件處理,這種情況通常并不是由于幀渲染導(dǎo)致的,但等待時(shí)長(zhǎng)如果違背了 RAIL 模型,會(huì)嚴(yán)重阻塞(BLOCK)用戶行為,該類型的卡頓通常會(huì)采用別的指標(biāo)進(jìn)行衡量。
?Response 響應(yīng)時(shí)間:系統(tǒng)應(yīng)當(dāng)在 100ms 內(nèi)對(duì)用戶的輸入作出響應(yīng),這種輸入表示任何用戶的交互行為,比如輸入文本、點(diǎn)擊、切換表單、開(kāi)啟動(dòng)畫等。在不阻塞用戶交互行為的前提下可以對(duì)一些耗時(shí)昂貴的工作進(jìn)行預(yù)運(yùn)算,對(duì)于耗時(shí) 500ms 以上的工作項(xiàng)應(yīng)當(dāng)通過(guò)信息反饋提前告知用戶。
Animation 動(dòng)畫:對(duì)于動(dòng)畫的交互,如 touchmove、scroll 等需要在 16ms 內(nèi)作出響應(yīng),而動(dòng)畫的幀率期望能夠達(dá)到每秒 60 幀,即折算一幀 16.6ms,而實(shí)際上渲染一幀的動(dòng)畫過(guò)程瀏覽器還存在著大量的樣式計(jì)算、布局計(jì)算、線程調(diào)度、圖層合成、光柵化等過(guò)程,因此純 JS 的線程執(zhí)行耗時(shí)應(yīng)當(dāng)控制在 10ms 內(nèi)。
Idle 最大化空閑時(shí)間:空閑時(shí)間可以用來(lái)完成優(yōu)先級(jí)不高的任務(wù),通過(guò) 50ms 為基準(zhǔn)將延遲任務(wù)進(jìn)行分片分組執(zhí)行,目的時(shí)為了能夠預(yù)留 50ms 的空閑時(shí)間給到主線程來(lái)對(duì)用戶的輸入進(jìn)行即時(shí)響應(yīng),從而完成 100ms 內(nèi)響應(yīng)用戶的標(biāo)準(zhǔn)。
Load:1s 內(nèi)完成站點(diǎn)加載。
?
上文中提到的很多名詞概念,在此也做簡(jiǎn)單介紹。
幀率(FPS):指的是一秒內(nèi)合成幀的數(shù)量,常用 FPS 來(lái)描述,60FPS 表示一秒內(nèi)合成 60 幀。
刷新率(Hz):指的是一秒內(nèi)屏幕的刷新次數(shù),安卓機(jī)通常為 60Hz。
VSync(垂直同步):是一種定時(shí)中斷技術(shù),時(shí)間間隔為 16.6ms,VSync 信號(hào)保證了畫面內(nèi)的數(shù)據(jù)來(lái)源同步,磨平了屏幕刷新方式帶來(lái)的上下屏數(shù)據(jù)來(lái)源的差異性。通常與雙緩存、三緩存技術(shù)共同解決 ”畫面撕裂“ 帶來(lái)的卡頓。此處介紹一個(gè)可用于測(cè)試設(shè)備是否支持 VSync 的在線工具 設(shè)備測(cè)試 VSync。
雙緩存機(jī)制:通過(guò)設(shè)計(jì)兩種類型的緩存器 Back Buffer、Frame Buffer 分別為 CPU 數(shù)據(jù)處理以及屏幕讀取數(shù)據(jù)服務(wù),解決了幀渲染過(guò)程中同時(shí)存在的讀寫邏輯沖突。
三緩存機(jī)制:CPU 與 GPU 在 Back Buffer 的使用權(quán)上存在競(jìng)爭(zhēng)關(guān)系,導(dǎo)致 GPU 占用時(shí)間段內(nèi) CPU 線程處于閑置狀態(tài),從而導(dǎo)致合成幀耗時(shí)較長(zhǎng),三緩存將 CPU 與 GPU 對(duì)緩存器的使用狀態(tài)也進(jìn)行了隔離。
如何衡量卡頓
???FPS 與卡頓的關(guān)系
一般來(lái)說(shuō),我們通過(guò)頁(yè)面的 FPS 來(lái)作為衡量頁(yè)面卡頓的指標(biāo),但是用 FPS 來(lái)做卡頓的描述并不精確,舉個(gè)例子:
電影的播放的 FPS = 24 ,但是在電影播放過(guò)程并不會(huì)出現(xiàn)卡頓現(xiàn)象,說(shuō)明了 FPS 低于 30 也不表示畫面卡頓,從定義上來(lái)看 FPS 僅描述了一秒內(nèi)的畫面繪制次數(shù),如果一秒內(nèi)頁(yè)面沒(méi)有任何繪制的需求,F(xiàn)PS = 0 ?是很正常的。
對(duì)應(yīng)的,F(xiàn)PS = 60 的頁(yè)面如果在前 200ms 僅僅完成了首幀的渲染而剩余 800ms 完成了剩余 59 幀的渲染,保證了一秒內(nèi)的幀數(shù) , 但是仍然會(huì)帶來(lái)卡頓的現(xiàn)象。
???新的衡量指標(biāo)
SM(SMoothes) 是 Android 端提出的相比于 FPS 更加準(zhǔn)確的衡量卡頓現(xiàn)象的指標(biāo),SM 更好的考慮了單位時(shí)長(zhǎng)內(nèi)的有效幀數(shù)占比,SM 計(jì)算公式如下:
SM = FPS * (單位時(shí)長(zhǎng)的總幀數(shù) - 單位時(shí)長(zhǎng)丟幀數(shù)) / 單位時(shí)長(zhǎng)總幀數(shù)
瀏覽器動(dòng)畫渲染
?
為了能夠更好的跟蹤動(dòng)畫渲染過(guò)程的卡頓,我們應(yīng)該更加清晰的去認(rèn)知瀏覽器渲染的原理和過(guò)程,動(dòng)畫渲染的原理大圖如下:
?
對(duì)上圖進(jìn)行鏈路總結(jié)如下:
CPU 負(fù)責(zé)處理 復(fù)雜的控制邏輯以及數(shù)據(jù)邏輯,在瀏覽器的渲染過(guò)程中生成 DOM 樹(shù)、CSSOM 樹(shù)、樣式計(jì)算、布局計(jì)算、圖層生成等,最后將矢量數(shù)據(jù)提交給合成器以及 GPU。
GPU 接受圖層圖塊信息,并基于強(qiáng)大的硬件能力對(duì)圖層進(jìn)行分割切片、柵格化。GPU 從結(jié)構(gòu)上擁有更多的 ALU 單元,更擅長(zhǎng)大量重復(fù)的計(jì)算以及矩陣變換,能夠以流式并行的模式快速完成位圖繪制,并將紋理數(shù)據(jù)存入緩存器。
顯示器通過(guò) VSync 信號(hào)強(qiáng)制拉齊幀率與刷新率,當(dāng)信號(hào)來(lái)臨時(shí)訪問(wèn)前緩存器拿到最新的位圖數(shù)據(jù)并用于上屏。
???GPU扮演的角色
我們常說(shuō)的硬件加速其實(shí)是通過(guò) GPU 的特性來(lái)緩解 CPU 的計(jì)算壓力,相比于 CPU,GPU擁有更多的 ALU(算數(shù)邏輯單元),其特有的流式并行計(jì)算模式在矩陣變換的計(jì)算上有天然的優(yōu)勢(shì),同時(shí)采用 GPU 的硬件加速可以對(duì)待處理的元素進(jìn)行圖層提升,實(shí)現(xiàn)了在畫布更新過(guò)程中隔離非合成元素的目的,等到生成結(jié)束后通過(guò)相應(yīng)位置的替換完成圖層的合成,從而減少了 CPU 線程內(nèi)回流和重繪的計(jì)算過(guò)程。
?
值得注意的是,瀏覽器本身為我們完成了很多優(yōu)化的策略,比如在使用 transform、opacity 屬性的時(shí)候?yàn)g覽器會(huì)自動(dòng)開(kāi)啟 GPU 加速,因此更推薦在動(dòng)畫過(guò)程中通過(guò)上述屬性進(jìn)行處理。除此之外,通過(guò) will-change 定義的 CSS 屬性值,瀏覽器也會(huì)預(yù)先將其關(guān)聯(lián)的元素提升到新的圖層,從而避免刷新屏幕時(shí)出現(xiàn)回流和重繪的過(guò)程。
???合理避免回流和重繪
事實(shí)上,是否避免了回流和重繪的過(guò)程取決于對(duì)應(yīng)的元素是否真正被提升到了合成層(Composite Layer)。即使使用了 transform、opacity 兩個(gè)屬性,如果瀏覽器不支持硬件加速導(dǎo)致圖層沒(méi)有被提升,那么在更新的過(guò)程中仍然會(huì)因此回流和重繪。
?
在此對(duì)常用于提升圖層的方法進(jìn)行羅列:
3D transforms: translate3d, translateZ 等;
video, canvas, iframe 等元素;
通過(guò) Element.animate() 實(shí)現(xiàn)的 opacity 動(dòng)畫轉(zhuǎn)換;
通過(guò) СSS 動(dòng)畫實(shí)現(xiàn)的 opacity 動(dòng)畫轉(zhuǎn)換;
position: fixed;
will-change;
filter;
有合成層(Composite Layer)后代同時(shí)本身 overflow 不為 visible(如果本身是因?yàn)槊鞔_的定位因素產(chǎn)生的 SelfPaintingLayer,則需要 z-index 不為 auto)
瀏覽器工作流程
到此我們明確了在瀏覽器動(dòng)畫繪制過(guò)程中 CPU 以及 GPU 扮演的角色,為了更加清晰的明確一幀內(nèi) CPU 與 GPU 負(fù)責(zé)的具體工作,我們需要對(duì)瀏覽器內(nèi)核的工作有一定的了解。
?
首先,瀏覽器是多進(jìn)程工作的,進(jìn)程結(jié)構(gòu)如下:
我們常說(shuō)的瀏覽器內(nèi)核其實(shí)就是渲染進(jìn)程(渲染引擎)。渲染引擎將從網(wǎng)絡(luò)層獲取請(qǐng)求的文檔內(nèi)容并解析渲染,整個(gè)渲染引擎的工作流程是漸進(jìn)的,渲染引擎的工作流程如圖所示:
對(duì)于其中的每一個(gè)部分展開(kāi)解釋有些復(fù)雜,本文不多做贅述。?
本文還是更加聚焦于畫面渲染的 Layout 以及 Painting 過(guò)程,從這個(gè)角度來(lái)看,單幀內(nèi)的渲染引擎主線程過(guò)程的 Pipline 如下圖:
引入 GPU 加速計(jì)算后的主線程 Pipline 如下圖:
對(duì)上述 Pipline 的各節(jié)點(diǎn)解讀如下:
Input event Handlers 代表瀏覽器的輸入事件回調(diào),該事件會(huì)被瀏覽器進(jìn)程(Brower Process)捕捉,隨后瀏覽器進(jìn)程會(huì)將事件類型(如touchstart)及其坐標(biāo)發(fā)送給渲染進(jìn)程(Renderer Process),渲染進(jìn)程通過(guò)查找事件目標(biāo)并運(yùn)行附加的事件偵聽(tīng)器來(lái)處理事件。
?requestAnimationFrame允許定義一個(gè)回調(diào)函數(shù),可提供給用戶在當(dāng)前幀布局計(jì)算以及繪制之前做一些預(yù)處理操作。
-
CPU 在 ParseHTML ?過(guò)程負(fù)責(zé)將瀏覽器不能識(shí)別的 HTML 文本轉(zhuǎn)換為瀏覽器能識(shí)別的 DOM 對(duì)象。
-
CPU 在 RecalcStyles 過(guò)程會(huì)解析 CSS 文件,依據(jù) CSS 的樣式繼承以及層疊規(guī)則計(jì)算 DOM 節(jié)點(diǎn)的每一個(gè)元素的具體樣式,并保存在 ComputedStyle 結(jié)構(gòu)中。
-
CPU 在 Layout 過(guò)程基于 DOM Tree 以及 Compouted Style 計(jì)算元素布局信息并生成 Layout Tree(RenderObject), 在該過(guò)程中不可見(jiàn)的 DOM 節(jié)點(diǎn)會(huì)被忽略,例如 head 標(biāo)簽下的全部?jī)?nèi)容以及 display = none 的 DOM 節(jié)點(diǎn)。
-
CPU 在 Update Layer Tree 過(guò)程負(fù)責(zé)將相同 z 空間坐標(biāo)的 Layout Tree(RenderObject) 歸并到相同的 Paint Layer(RenderLayer),從而保證頁(yè)面元素的合成順序。通常情況下,并不是布局樹(shù)的每個(gè)節(jié)點(diǎn)都包含一個(gè)圖層,如果一個(gè)節(jié)點(diǎn)沒(méi)有對(duì)應(yīng)的層,那么這個(gè)節(jié)點(diǎn)就從屬于父節(jié)點(diǎn)的圖層,但不管怎樣,最終每一個(gè)節(jié)點(diǎn)都會(huì)直接或者間接地從屬于一個(gè)層。
CPU 在 Paint 過(guò)程干了兩件事:第一件事是完成繪制(Painting),即生成新生成 / 修改元素的繪制信息,包括圖形信息,文本信息。第二件事是完成柵格化(Rasterization),對(duì) Painting 的繪制信息進(jìn)行消費(fèi),對(duì)于沒(méi)有 GPU 加速的情況下,瀏覽器會(huì)通過(guò) CPU 進(jìn)行軟件柵格化,軟件渲染器沒(méi)有使用 GL 將內(nèi)容紋理塊復(fù)制到后臺(tái)緩沖區(qū),而是使用 Skia( 2D 繪圖庫(kù))的軟件光柵化器來(lái)執(zhí)行復(fù)制(并執(zhí)行任何必要的矩陣計(jì)算和裁剪)。
CPU 在 Composite 過(guò)程將圖層信息提交(commit)到 合成線程(Compositor Thread) 中,在此線程中會(huì)對(duì)圖層進(jìn)行分塊生成圖塊(圖塊是柵格操作的單位)同時(shí)借助 GPU 對(duì)額外的屬性(如:will-change 聲明的元素,啟用硬件加速的 canvas)進(jìn)行處理(圖層變換、合成)。
GPU 在 Rasterize 過(guò)程對(duì)圖塊紋理進(jìn)行 柵格化 處理。
合成線程會(huì)在所有圖層被 Rasterize 后打包發(fā)送到 GPU 進(jìn)程,此時(shí)標(biāo)志著一幀結(jié)束。
頁(yè)面的所有信息在 GPU 內(nèi)被處理后傳入雙緩存的 Back Buffer 中,下次垂直同步信號(hào)到達(dá)后互換前后緩存區(qū)位置,完成上屏。
到此,可以回答本章節(jié)開(kāi)篇提出的問(wèn)題,CPU 給 GPU 傳遞的數(shù)據(jù)是經(jīng)過(guò)布局、繪制計(jì)算的到的矢量圖信息,而 GPU 只負(fù)責(zé)對(duì)矢量圖進(jìn)行像素化(光柵化)以及著色后將其存放到幀緩存區(qū),等待下一個(gè) VSync 信號(hào)的同步。
解決方案
回到移動(dòng)端本身的問(wèn)題,低端機(jī)面臨的性能瓶頸問(wèn)題往往由于其硬件能力不足導(dǎo)致的,如 CPU 性能(主頻大小、Cache 容量)、內(nèi)核數(shù)、內(nèi)存大小、DRAM 大小、是夠支持 GPU 加速以及 GPU 核數(shù)等。對(duì)于同樣的頁(yè)面邏輯,在低端機(jī)上執(zhí)行,往往會(huì)帶來(lái)更高的 CPU占有率從而導(dǎo)致幀數(shù)丟失、更高的內(nèi)存占有率從而帶來(lái)更多的數(shù)據(jù)交換損失,而一些不支持 GPU 的機(jī)型中,單幀內(nèi)的動(dòng)畫會(huì)依賴 CPU 進(jìn)行軟件渲染,這個(gè)過(guò)程會(huì)大量占用 CPU 主線程從而帶來(lái)丟幀。
?
在了解低端機(jī)面臨的瓶頸問(wèn)題后,我們應(yīng)該通過(guò)以下策略最大限度的降低性能損耗:
開(kāi)啟適當(dāng)?shù)挠布铀伲℅PU 加速),合理使用 CSS 屬性進(jìn)行動(dòng)畫繪制,如 transform、opacity、filter等。
采用 will-change 對(duì)比較復(fù)雜的元素處理進(jìn)行單獨(dú)圖層的提升(切忌對(duì)所有元素使用,過(guò)量的圖層會(huì)加劇 GPU 合成時(shí)的功耗)。
最小化動(dòng)畫的范圍,可以有效減少幀數(shù)據(jù)(位圖)的大小,降低 GPU 訪問(wèn)主存的時(shí)間損耗。
使用脫離文檔流的方式對(duì)元素進(jìn)行動(dòng)畫,減少回流。
避免通過(guò)父級(jí)元素對(duì)子元素進(jìn)行訪問(wèn),樣式的訪問(wèn)鏈路過(guò)程會(huì)加劇樣式樹(shù)計(jì)算時(shí)的時(shí)間損耗。
盡可能少的訪問(wèn) offsetWidth、 top 等元素樣式屬性,因?yàn)檫@些屬性會(huì)引發(fā)回流和重繪,如果必須訪問(wèn)時(shí),可以進(jìn)行數(shù)據(jù)緩存,同時(shí)可以在 rAF 階段進(jìn)行訪問(wèn),因?yàn)?rAF 后瀏覽器會(huì)進(jìn)行布局的 recalculate 。
對(duì)于沒(méi)有 GPU 加速的機(jī)型,可采用降低動(dòng)畫的影響范圍,降低紋理的尺寸的方式等加速主線程,從而提升頁(yè)面響應(yīng)速度。
長(zhǎng)任務(wù)切片,將連續(xù)串行的任務(wù)通過(guò)優(yōu)先級(jí)設(shè)置和任務(wù)調(diào)度的方式切割,從而減少長(zhǎng)任務(wù)占用主線程時(shí)無(wú)法及時(shí)對(duì)用戶行為作出響應(yīng)。
團(tuán)隊(duì)介紹
我們是大淘寶技術(shù)營(yíng)銷與平臺(tái)策略技術(shù)團(tuán)隊(duì),是大淘寶技術(shù)的核心部門之一,負(fù)責(zé)手機(jī)淘寶核心用戶產(chǎn)品——搜索、拍立淘,并且支撐雙11、618等大型活動(dòng),聚劃算、百億補(bǔ)貼、天天特賣、淘寶好價(jià)等營(yíng)銷產(chǎn)品。這里有基于商品的搜索引擎建設(shè)對(duì)搜索與終端智能業(yè)務(wù)的探索,還有招商、選品、搭建、投放的活動(dòng)支撐鏈路建設(shè)以及優(yōu)惠券、跨店滿減、直降、會(huì)員卡等多種營(yíng)銷工具的創(chuàng)新和沉淀。我們的目標(biāo)是以平臺(tái)化、數(shù)據(jù)化、智能算法等方式支撐大淘寶為主的集團(tuán)核心平臺(tái)營(yíng)銷場(chǎng)景,全力為淘寶天貓打造有樂(lè)趣的購(gòu)物體驗(yàn),為數(shù)億用戶提供優(yōu)質(zhì)服務(wù)。
¤?拓展閱讀?¤
3DXR技術(shù)?|?終端技術(shù)?|?音視頻技術(shù)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-458598.html
服務(wù)端技術(shù)?|?技術(shù)質(zhì)量?|?數(shù)據(jù)算法文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-458598.html
到了這里,關(guān)于移動(dòng)端瀏覽器性能優(yōu)化探索的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!