轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/milado_nju/
# Chromium渲染主循環(huán)(mainloop)和requestAnimationFrame
## 概述
曾經(jīng)寫過一段JavaScript代碼,因?yàn)樯婕暗叫枰h(huán)調(diào)用某個(gè)函數(shù)來實(shí)現(xiàn)動(dòng)畫的功能,很自然地,我想到了使用setInterval函數(shù)(或者setTimeout,大家是否有類似經(jīng)歷呢?),然后心滿意足地很快的搞定。結(jié)束后,朋友幫忙閱讀了一下代碼,他提醒我是不是可以考慮使用requestAnimationFrame。之前一直知道這個(gè)函數(shù),也知道一些它的一些優(yōu)點(diǎn),問題是為什么呢?本著追究到底的精神,決定還是去閱讀一下WebKit相關(guān)代碼和一些相關(guān)文檔,了解它們背后的故事。好吧,本章我將和大家一起來學(xué)習(xí)和探討這背后的故事…
## 背景
接觸過JavaScript的讀者應(yīng)該有過了解或者使用setTimeout或者setInterval的經(jīng)歷,其功能是在每個(gè)時(shí)間間隔之后一次性或者重復(fù)多次執(zhí)行一段JavaScript代碼(稱為回調(diào)函數(shù)),以完成特定的動(dòng)畫要求。但是,這里面有還有些疑問:
1. 時(shí)間間隔應(yīng)該設(shè)置為多少才合適呢?跟屏幕的分辨率有關(guān)系嗎?
2. 設(shè)置的時(shí)間間隔會(huì)按照預(yù)想的執(zhí)行嗎?動(dòng)畫會(huì)被平滑地顯示出效果嗎?
3. 回調(diào)函數(shù)是復(fù)雜的好還是簡(jiǎn)單的好呢?應(yīng)該如何編寫才能效率高呢?
4. 與平臺(tái)和瀏覽器相關(guān)嗎?如何適應(yīng)不同平臺(tái)呢?
這對(duì)setTimeout和setInterval來說很重要。如果對(duì)mainloop機(jī)制和渲染機(jī)制有一定了解的讀者來說,上面這幾條其實(shí)是非常難做到地,哪怕是較為接近理想的結(jié)果。
幸運(yùn)地是,總是有聰明的人來幫助大家解決難題。對(duì)問題提出一個(gè)漂亮解決方案的是mozilla的Robert O’Callahan。他的靈感和依據(jù)來源于CSS。CSS是知道動(dòng)畫什么時(shí)候發(fā)生,所以能夠較為準(zhǔn)確的知道什么時(shí)候刷新UI。對(duì)于JavaScript來說,是不是也可以根據(jù)類似的機(jī)制呢?答案是肯定地。其做法是增加一個(gè)新的方法requestAnimationFrame, 該方法告訴瀏覽器JavaScript想發(fā)起一個(gè)動(dòng)畫幀,然后在動(dòng)畫幀繪制之前,需要做一些動(dòng)作,這樣瀏覽器可以根據(jù)需要來優(yōu)化自己的mainloop機(jī)制和調(diào)用時(shí)間點(diǎn),以達(dá)到較好地平衡效果。
好吧,下面來看看mainloop機(jī)制及其工作原理。
## 渲染mainloop
因?yàn)閏hromium是多進(jìn)程的結(jié)構(gòu)(參看Chromium多進(jìn)程架構(gòu)篇),所以,跟一般瀏覽器不一樣的是,Browser進(jìn)程UI用戶界面的mainloop和Renderer進(jìn)程的主線程的mainloop不是同一個(gè),分別位于兩個(gè)不同的進(jìn)程,所以UI和渲染可以互相不影響,聽起來這好像很不錯(cuò),是的,但是問題依然存在,那就是Renderer進(jìn)程的渲染工作和JavaScript的執(zhí)行工作都在其主線程中,由mainloop來負(fù)責(zé)調(diào)度完成,所以競(jìng)爭(zhēng)依然存在。
大致過程是一個(gè)大的循環(huán)加上一個(gè)事件隊(duì)列,具體的過程,如下圖所示。當(dāng)隊(duì)列中有事件時(shí),從隊(duì)列中取出第一個(gè)事件,設(shè)置相應(yīng)的狀態(tài)信息,處理該事件及其對(duì)應(yīng)的處理函數(shù),直到該函數(shù)處理完后,才重新檢查隊(duì)列中是否有事件。如果有,繼續(xù)處理;如果沒有,則繼續(xù)等待。這其中可以看出,如果隊(duì)列中事件多的時(shí)候,那么很多事件可能來不及處理,從而造成比較大的延時(shí),因而事件的平均等待時(shí)間會(huì)比較長(zhǎng)。同時(shí),如果事件的處理函數(shù)需要的時(shí)間很長(zhǎng),就會(huì)造成后面的事件一直在等待,同樣會(huì)增加事件的平均等待時(shí)間。而當(dāng)隊(duì)列比較空閑時(shí)或者事件的處理函數(shù)需要的時(shí)間比較短,則事件的平均等待時(shí)間會(huì)相對(duì)小很多。
##WebKit和Chromium中的實(shí)現(xiàn)
理解了mainloop之后,下面來看一看setTimeout和setInterval的實(shí)現(xiàn)。
來看一下它們的實(shí)現(xiàn):WebKit中setTimeout和setInterval的實(shí)現(xiàn)機(jī)制是類似的,區(qū)別在于后者是重復(fù)性的,見下圖所示的類圖關(guān)系。
WebKit會(huì)為DOM中的每個(gè)setTimeout和setInterval的調(diào)用創(chuàng)建一個(gè)DOMTimer,而后該對(duì)象會(huì)由存儲(chǔ)TLS(thread localstorage)中的ThreadTimers負(fù)責(zé)管理,其內(nèi)部其實(shí)是一個(gè)最小堆,每次取timeout時(shí)間最小的,同時(shí),時(shí)間相同的Timer可以合并。
當(dāng)Timer超時(shí)后,Chromium清除該Timer對(duì)象,同時(shí)調(diào)用相應(yīng)的回調(diào)函數(shù),回調(diào)函數(shù)通常會(huì)更新頁面的樣式和布局,這會(huì)觸發(fā)relayout,從而觸發(fā)立即重新繪制一個(gè)新幀。
結(jié)合上面的描述,我們大致地總結(jié)setTimeout和setInterval主要不足就是:
1. setTimeout和setInterval從不考慮瀏覽器內(nèi)部發(fā)生了其他什么事,它只要求瀏覽器在某個(gè)時(shí)間之后調(diào)用它的回調(diào)函數(shù),無論瀏覽器很繁忙或者頁面被隱藏(雖然某些瀏覽器做了這方面的優(yōu)化,例如chromium);
2. setTimeout和setInterval只要求瀏覽器做什么,而不管瀏覽器能不能做到(例如mainloop有很多事件需要處理),這有點(diǎn)強(qiáng)人所難,而且會(huì)帶來極大的資源浪費(fèi)。舉個(gè)例子,例如屏幕的刷新率是60HZ,但是設(shè)置的時(shí)間間隔是5ms,其實(shí)對(duì)用戶來說根本看不到這些變化,但是額外需要消耗更多的CPU資源,太不環(huán)保了…
3. setTimeout和setInterval可能是編程風(fēng)格方面的考慮。如果每一幀可能在不同的代碼出需要設(shè)置回調(diào)函數(shù),一個(gè)方法是統(tǒng)一到一個(gè)地方,但是這有點(diǎn)勉為其難,另一個(gè)方法是分別用setInterval設(shè)置它們,這個(gè)方法的問題是,瀏覽器可能需要計(jì)算更多次,刷新更多次的屏幕,唉。
現(xiàn)在再來看看requestAnimationFrame的實(shí)現(xiàn),看看其如何解決這些不足之處的。其原理就是其會(huì)申請(qǐng)繪制下一幀,至于什么時(shí)候不知道,由瀏覽器決定,只需要瀏覽器在繪制下一幀前執(zhí)行其設(shè)置的回調(diào)函數(shù),完成JavaScript對(duì)動(dòng)畫所做的設(shè)置和邏輯即可。基本過程是這樣的:
1. JavaScript調(diào)用requestAnimationFrame,因而相應(yīng)的webkit和chromium會(huì)調(diào)度一個(gè)需要繪制下一幀的事件,該事件會(huì)將requestAnimationFrame的調(diào)用上下文和回調(diào)函數(shù)記錄下來;
2. 上面的請(qǐng)求會(huì)觸發(fā)Chromium更新頁面內(nèi)容的事件,該事件被mainloop調(diào)度處理后,會(huì)檢查是否需要調(diào)用動(dòng)畫的相關(guān)處理,因?yàn)橛袆?dòng)畫需要處理,所以會(huì)依次調(diào)用那些回調(diào)函數(shù),JavaScript引擎會(huì)更新相應(yīng)的CSS屬性或者DOM樹修改;
3. Chromium觸發(fā)重新計(jì)算layout(參看layout章節(jié)),更新自己的Renderer樹(參看webkit渲染基礎(chǔ)章節(jié)),而后繪制,完成一幀的渲染。
下圖是一個(gè)上述過程對(duì)應(yīng)的狀態(tài)轉(zhuǎn)換圖,來源于chromium的官方網(wǎng)站,看著的確比較饒人,可以先理解一下其中幾個(gè)主要的概念:
Floortime:指的是繪制下一幀之前需要等待的事件間隔
Invalidation:觸發(fā)重新繪制請(qǐng)求的操作;
scheduleAnimation:JavaScript調(diào)用requestAnimationFrame所引起的WebKit內(nèi)部請(qǐng)求調(diào)度動(dòng)畫的操作;
這些狀態(tài)的轉(zhuǎn)換倒是說明了,requestAnimationFrame可以很好地和Chromium內(nèi)部的繪制過程結(jié)合,從而達(dá)到比較好的性能。
為了實(shí)現(xiàn)更好的性能,chromium中對(duì)requestAnimationFrame有三個(gè)設(shè)計(jì)原則
1. 當(dāng)頁面不可見時(shí),其回調(diào)函數(shù)不會(huì)被調(diào)用,這可以減少CPU和GPU的使用率,更環(huán)保嘛;
2. 其最大調(diào)用頻率不會(huì)超過60hz,無論屏幕的刷新率是多少,因而回調(diào)函數(shù)也不會(huì)每秒調(diào)用超過60次,這是因?yàn)?0FPS已經(jīng)能夠滿足UI流暢的要求了,更頻繁的刷新效果不明顯;
3. 只有當(dāng)頁面真正開始渲染時(shí),回調(diào)函數(shù)才會(huì)被調(diào)用。
為了對(duì)比二者的性能上的差異,我測(cè)試了GuiMark中HTML5Charting Test benchmark,修改里面一些代碼(其缺省使用的是setInterval,改為requestAnimationFrame作對(duì)比),從實(shí)際測(cè)試的效果上看,在Google Chrome中,兩者相差不是特別大,使用了requestAnimationFrame的benchmark的FPS大概只好了1~2FPS,所以chrome對(duì)timer機(jī)制的優(yōu)化做地應(yīng)該相當(dāng)不錯(cuò)。如果你遇到了其他差別比較大的例子,歡迎跟我和大家分享。
Google Chrome對(duì)其處理的比較好不代表其他瀏覽器也是,所以各位還是在編程時(shí)候多考慮考慮,多思考思考,為了更好的性能,為了環(huán)?!?/p>
## 設(shè)計(jì)機(jī)制帶來的編程考慮
最后,結(jié)合mainloop和requestAnimationFrame的設(shè)計(jì)原理和機(jī)制,看一看它們帶給我們?cè)诰帉慗avaScript代碼時(shí)有哪些方面的思考和便利:
1. 回調(diào)函數(shù)不能太大,不能占用太長(zhǎng)時(shí)間,否則會(huì)影響頁面的響應(yīng)和繪制的頻率;
2. requestAnimationFrame不需要設(shè)置間隔時(shí)間,不同刷新率的間隔時(shí)間不一樣,這完全由瀏覽器來控制,而不需要JavaScript程序員操心;
3. 回調(diào)函數(shù)無需合并,程序員可以在任意位置設(shè)置回調(diào)函數(shù),它們可以被瀏覽器集中處理,而無需要一個(gè)統(tǒng)一的入口。
## 源文件目錄
third_party/WebKit/Source/WebCore/page/
支持requestAnimationFrame,setTimeout和setInterval的絕大多數(shù)基礎(chǔ)設(shè)施都在這里,建議在該目錄下搜索這些關(guān)鍵字即可
third_party/WebKit/Source/WebCore/platform
Timer方面的一些支持
## 參考文獻(xiàn)
1. http://dev.chromium.org/developers/design-documents/requestAnimationFrame-implementation
2. http://www.cnblogs.com/rubylouvre/archive/2011/08/22/2148793.html
3. http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestAnimationFrame/
4. https://developer.mozilla.org/en-US/docs/DOM/window.requestAnimationFrame
5. http://www.w3.org/TR/animation-timing/#requestAnimationFrame
6. http://creativejs.com/resources/requestAnimationFrame/
7. http://www.craftymind.com/factory/guimark2/HTML5ChartingTest.html
By yongsheng@chromium.org
聯(lián)系客服