Shiki’s Weblog

ESウェブブラウザ通信 - CSSレイアウト処理のマルチスレッド化

2012/05/09 #ESウェブブラウザ通信

前回は、HTTPリクエストの処理を別スレッドで実行させることによって、ブラウザのメインループの処理とは非同期にもっと効率よくサーバーからデータを取得してくるようになりました、という報告をしました。それを読んで、『サーバーから応答が帰ってきてから次のリクエストを出すのでは、リクエストの送信の時間分まだロスしているのでは?』と思われた方がいたら鋭いです。HTTP 1.1では規格上はサーバーからの応答を待つことなく先にリクエストをまとめて送ってしまうパイプライン処理についても規定されています。ただ規定と実世界での実装は別ということもあるみたいなのです[参考]。そういう流れで、最近話題のSPDYとかHTTPbisのことを見ていくとおもしろいのじゃないかな、と思います。今回は前回課題の一つとして挙げたカスケーディングやレイアウトといった処理をメインのスレッドとは別のスレッドで実行させる、という処理がESウェブ ブラウザに入ったのでその報告です。

カスケーディングとレイアウトをバックグラウンドで処理

下図はr2695でのabout:画面ですが(r2694からabout URIスキームに対応しています)、右上の歯車のアイコンが以前と変わっていて、この歯車がページのロード中はくるくる回るようになりました。以前はブラウザが固まったように見えてしまっていた時間に、こういったアニメーションを表示することで、ユーザーにブラウザが処理中だということを伝えられるようにしたわけです。

r2695r2695

このようなことが可能になったのは、r2679以降の修正でカスケーディング処理とレイアウト処理が別スレッドで実行されるようになったためです。この場合のカスケーディング処理、レイアウト処理、レンダリング処理が各フレームごとのどのように実行されているかを示したものが下図になります。

カスケーディングとレイアウト処理の並列実行

ブラウザ内部では、メイン スレッドはレンダリング用のボックス ツリーを使って描画処理を行います。一方、バックグラウンド スレッドはレンダリング用のボックス ツリーには一切触れずに、新しい別のボックス ツリーを構築していきます。メイン スレッドはバックグラウンド スレッドの処理が完了すると、古いレンダリング用のボックス ツリーを破棄して、バックグラウンド スレッドが構築した新しいボックス ツリーにレンダリング用のボックス ツリーを切り替える、という処理を繰りかえしています。この枠組みでは、レンダリングをほぼ一定の周期で実行できるので、GIFアニメ(r2681から対応しています)などをスムーズに表示することができます。実際に、ページのロード中に回っているナビゲーターの歯車もGIFアニメです。ナビゲーターはbeforeunloadイベントが発生してからloadイベントが発生するまで歯車の画像をpngの静止画からgifのアニメ画像にbackground-imageプロパティを使って変更しています(r2686)。また前回実装したカスケーディング結果を再利用してレイアウトだけやり直す、という処理もマルチスレッドに対応させています(r2703)。例えば、上図のフレーム#10, #14ではレイアウトだけ再実行させている様子を示しています。この場合、それぞれ#12, #16から新しいレイアウト結果に表示が切り替わることになります。上図フレーム#5, #6の部分では、バックグラウンド スレッドはカスケーディング処理が終わっても直ちにレイアウト処理は開始しないで、フレーム#6まで待っている様子を示しています。これは現状ESウェブ ブラウザではJavaScriptの実行はメイン スレッドだけに制限しているためです(一気にJavaScriptの並列実行まで進めてしまうとバグの切り分けが難しくなってしまうため)。そしてXBL2では、シャドーツリーがドキュメント内に展開されたあとで、xblEnteredDocument()メソッドを呼び出さないといけません。従来はカスケーディング処理中に呼び出していたのですが、それだとJavaScriptが並列に実行されてしまいます。そのため、カスケーディング処理が終わったらバックグラウンドスレッドは一旦ポーズします。そしてメインスレッドがxblEnteredDocument()メソッドを呼び出したあとで、レイアウト処理を再開するように制御しています。なお上図では省略していますが、HTMLドキュメントのパース処理はまだメイン スレッドの側で行っています。そのため、大きなドキュメントのパース中はブラウザが止まったように見える時間があります。これも「JavaScriptの実行はメイン スレッドだけで」という制約からくるものです。バックグラウンドでのJavaScriptの実行については、Web Workersとの関連もあるので、どこかで見直すことにします。また現状ではマウス カーソルが位置している要素が変わると、:hover擬似クラスの適用に変更があるかもしれないということで、カスケーディング処理からやり直すような実装になっています。そのためスクロールなどマウスを使った操作が非常に重く感じられる場合があります。この点については、CSS 2.1の仕様書5.11.3でも、

User agents are not required to reflow a currently displayed document due to pseudo-class transitions.

という記載があります。ユーザーにとって使い勝手が悪くなるようなことまでして、:hoverを忠実に実装しないといけない、ということではない、ということですね。このあたりの処理は高速化を試みると同時にうまい手の抜き方を見つけて実装する、ということも重要になってきそうです。

まとめ

今回はここまでです。 今回の枠組みに関しては、マルチスレッドと言ってもメイン スレッドから裏でこれをしておいて、という指示を出して、裏の処理が終わったらやはりメイン スレッドの側から結果を取得してくる(そのときはバックグラウンド スレッドは停止している)、ということなので、同期処理などで注意しないといけない箇所はそれほど多いわけではありません。それでもマルチスレッド対応ということでまだ修正が必要な部分が残っていたりしています。マルチスレッドのプログラムは、丁寧にチェックしておかないと予想外の場面でクラッシュしたりするのでやっぱり大変ですね。次回は今回宿題にした:hover擬似クラスの処理の改善などから進めて行く予定です。