理解WebKit和Chromium: 渲染主循環(main loop)和requestAnimationFrame

轉載請註明出處:http://blog.csdn.net/milado_nju/

# Chromium渲染主循環(mainloop)和requestAnimationFrame

## 概述

曾經寫過一段JavaScript代碼,因爲涉及到需要循環調用某個函數來實現動畫的功能,很自然地,我想到了使用setInterval函數(或者setTimeout,大家是否有類似經歷呢?),然後心滿意足地很快的搞定。結束後,朋友幫忙閱讀了一下代碼,他提醒我是不是可以考慮使用requestAnimationFrame。之前一直知道這個函數,也知道一些它的一些優點,問題是爲什麼呢?本着追究到底的精神,決定還是去閱讀一下WebKit相關代碼和一些相關文檔,瞭解它們背後的故事。好吧,本章我將和大家一起來學習和探討這背後的故事…

 

## 背景

接觸過JavaScript的讀者應該有過了解或者使用setTimeout或者setInterval的經歷,其功能是在每個時間間隔之後一次性或者重複多次執行一段JavaScript代碼(稱爲回調函數),以完成特定的動畫要求。但是,這裏面有還有些疑問:

1.      時間間隔應該設置爲多少才合適呢?跟屏幕的分辨率有關係嗎?

2.      設置的時間間隔會按照預想的執行嗎?動畫會被平滑地顯示出效果嗎?

3.      回調函數是複雜的好還是簡單的好呢?應該如何編寫才能效率高呢?

4.      與平臺和瀏覽器相關嗎?如何適應不同平臺呢?

這對setTimeout和setInterval來說很重要。如果對mainloop機制和渲染機制有一定了解的讀者來說,上面這幾條其實是非常難做到地,哪怕是較爲接近理想的結果。

幸運地是,總是有聰明的人來幫助大家解決難題。對問題提出一個漂亮解決方案的是mozilla的Robert O’Callahan。他的靈感和依據來源於CSS。CSS是知道動畫什麼時候發生,所以能夠較爲準確的知道什麼時候刷新UI。對於JavaScript來說,是不是也可以根據類似的機制呢?答案是肯定地。其做法是增加一個新的方法requestAnimationFrame, 該方法告訴瀏覽器JavaScript想發起一個動畫幀,然後在動畫幀繪製之前,需要做一些動作,這樣瀏覽器可以根據需要來優化自己的mainloop機制和調用時間點,以達到較好地平衡效果。

好吧,下面來看看mainloop機制及其工作原理。

 

## 渲染mainloop

因爲chromium是多進程的結構(參看Chromium多進程架構篇),所以,跟一般瀏覽器不一樣的是,Browser進程UI用戶界面的mainloop和Renderer進程的主線程的mainloop不是同一個,分別位於兩個不同的進程,所以UI和渲染可以互相不影響,聽起來這好像很不錯,是的,但是問題依然存在,那就是Renderer進程的渲染工作和JavaScript的執行工作都在其主線程中,由mainloop來負責調度完成,所以競爭依然存在。

大致過程是一個大的循環加上一個事件隊列,具體的過程,如下圖所示。當隊列中有事件時,從隊列中取出第一個事件,設置相應的狀態信息,處理該事件及其對應的處理函數,直到該函數處理完後,才重新檢查隊列中是否有事件。如果有,繼續處理;如果沒有,則繼續等待。這其中可以看出,如果隊列中事件多的時候,那麼很多事件可能來不及處理,從而造成比較大的延時,因而事件的平均等待時間會比較長。同時,如果事件的處理函數需要的時間很長,就會造成後面的事件一直在等待,同樣會增加事件的平均等待時間。而當隊列比較空閒時或者事件的處理函數需要的時間比較短,則事件的平均等待時間會相對小很多。


##WebKit和Chromium中的實現

理解了mainloop之後,下面來看一看setTimeout和setInterval的實現。

來看一下它們的實現:WebKit中setTimeout和setInterval的實現機制是類似的,區別在於後者是重複性的,見下圖所示的類圖關係。

WebKit會爲DOM中的每個setTimeout和setInterval的調用創建一個DOMTimer,而後該對象會由存儲TLS(thread localstorage)中的ThreadTimers負責管理,其內部其實是一個最小堆,每次取timeout時間最小的,同時,時間相同的Timer可以合併。

當Timer超時後,Chromium清除該Timer對象,同時調用相應的回調函數,回調函數通常會更新頁面的樣式和佈局,這會觸發relayout,從而觸發立即重新繪製一個新幀。


結合上面的描述,我們大致地總結setTimeout和setInterval主要不足就是:

1.      setTimeout和setInterval從不考慮瀏覽器內部發生了其他什麼事,它只要求瀏覽器在某個時間之後調用它的回調函數,無論瀏覽器很繁忙或者頁面被隱藏(雖然某些瀏覽器做了這方面的優化,例如chromium);

2.      setTimeout和setInterval只要求瀏覽器做什麼,而不管瀏覽器能不能做到(例如mainloop有很多事件需要處理),這有點強人所難,而且會帶來極大的資源浪費。舉個例子,例如屏幕的刷新率是60HZ,但是設置的時間間隔是5ms,其實對用戶來說根本看不到這些變化,但是額外需要消耗更多的CPU資源,太不環保了…

3.      setTimeout和setInterval可能是編程風格方面的考慮。如果每一幀可能在不同的代碼出需要設置回調函數,一個方法是統一到一個地方,但是這有點勉爲其難,另一個方法是分別用setInterval設置它們,這個方法的問題是,瀏覽器可能需要計算更多次,刷新更多次的屏幕,唉。

現在再來看看requestAnimationFrame的實現,看看其如何解決這些不足之處的。其原理就是其會申請繪製下一幀,至於什麼時候不知道,由瀏覽器決定,只需要瀏覽器在繪製下一幀前執行其設置的回調函數,完成JavaScript對動畫所做的設置和邏輯即可。基本過程是這樣的:

1.      JavaScript調用requestAnimationFrame,因而相應的webkit和chromium會調度一個需要繪製下一證的事件,該事件會將requestAnimationFrame的調用上下文和回調函數記錄下來;

2.      上面的請求會觸發Chromium更新頁面內容的事件,該事件被mainloop調度處理後,會檢查是否需要調用動畫的相關處理,因爲有動畫需要處理,所以會依次調用那些回調函數,JavaScript引擎會更新相應的CSS屬性或者DOM樹修改;

3.      Chromium觸發重新計算layout(參看layout章節),更新自己的Renderer樹(參看webkit渲染基礎章節),而後繪製,完成一幀的渲染。

下圖是一個上述過程對應的狀態轉換圖,來源於chromium的官方網站,看着的確比較饒人,可以先理解一下其中幾個主要的概念:

Floortime:指的是繪製下一幀之前需要等待的事件間隔

Invalidation:觸發重新繪製請求的操作;

scheduleAnimation:JavaScript調用requestAnimationFrame所引起的WebKit內部請求調度動畫的操作;

這些狀態的轉換倒是說明了,requestAnimationFrame可以很好地和Chromium內部的繪製過程結合,從而達到比較好的性能。


爲了實現更好的性能,chromium中對requestAnimationFrame有三個設計原則

1.      當頁面不可見時,其回調函數不會被調用,這可以減少CPU和GPU的使用率,更環保嘛;

2.      其最大調用頻率不會超過60hz,無論屏幕的刷新率是多少,因而回調函數也不會每秒調用超過60次,這是因爲60FPS已經能夠滿足UI流暢的要求了,更頻繁的刷新效果不明顯;

3.      只有當頁面真正開始渲染時,回調函數纔會被調用。

爲了對比二者的性能上的差異,我測試了GuiMark中HTML5Charting Test benchmark,修改裏面一些代碼(其缺省使用的是setInterval,改爲requestAnimationFrame作對比),從實際測試的效果上看,在Google Chrome中,兩者相差不是特別大,使用了requestAnimationFrame的benchmark的FPS大概只好了1~2FPS,所以chrome對timer機制的優化做地應該相當不錯。如果你遇到了其他差別比較大的例子,歡迎跟我和大家分享。

Google Chrome對其處理的比較好不代表其他瀏覽器也是,所以各位還是在編程時候多考慮考慮,多思考思考,爲了更好的性能,爲了環保…

 

## 設計機制帶來的編程考慮

最後,結合mainloop和requestAnimationFrame的設計原理和機制,看一看它們帶給我們在編寫JavaScript代碼時有哪些方面的思考和便利:

1.      回調函數不能太大,不能佔用太長時間,否則會影響頁面的響應和繪製的頻率;

2.      requestAnimationFrame不需要設置間隔時間,不同刷新率的間隔時間不一樣,這完全由瀏覽器來控制,而不需要JavaScript程序員操心;

3.      回調函數無需合併,程序員可以在任意位置設置回調函數,它們可以被瀏覽器集中處理,而無需要一個統一的入口。

 

## 源文件目錄

third_party/WebKit/Source/WebCore/page/

  支持requestAnimationFrame,setTimeout和setInterval的絕大多數基礎設施都在這裏,建議在該目錄下搜索這些關鍵字即可

third_party/WebKit/Source/WebCore/platform

  Timer方面的一些支持

 

## 參考文獻

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 [email protected]

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章