Webフロントエンド ハイパフォーマンスチューニングを読んだよ 前編

今回のエントリーは、初めての試みですが、読書感想文を書いていきたいと思います。今回感想文を書いていく本は、”Webフロントエンド ハイパフォーマンスチューニング” という本です。書籍の内容をすべて書くのは大変ですし、そもそも著作権的に怖いので特に大事だと思った部分についてまとめていきます。

Webパフォーマンスとは

この本では、冒頭にWebパフォーマンスとは何なのかということを定義してくれています。この本において、パフォーマンスの定義は以下のように定義されています。

ユーザーの様々な振る舞いに対してWebページが応答する速さ

ブラウザレンダリングの仕組み

第2章ではブラウザレンダリングの仕組みについての内容でした。レンダリングの仕組みは重要な部分だとは思いますが、普段意識するところではないため、知識があやふやになっていました。本書によると、パフォーマンスチューニングを正しく行うためには、パフォーマンス測定をする必要があり、測定の結果を正しく読み解くためにはブラウザレンダリングの仕組みについての知識や理解が重要になってくるそうです。

ブラウザ内のコンポーネント

ブラウザは通常いくつかのソフトウェアコンポーネントによって構成されています。 チューニングをするために知るべき2つのコンポーネントがあります。

  • レンダリングエンジン
  • JavaScript エンジン

■ レンダリングエンジン レンダリングエンジンとは、ブラウザの内部で利用されるHTMLの描画エンジンを指します。ブラウザそのものではなく、ウェブページのレンダリングのみを担当するソフトウェアコンポーネントです。
HTMLや画像ファイルやCSSやJavaScriptなどの各種リソースを読み取って、それを画⾯上の実際のピクセルとして描画します。描画のみを担当しているため、URLを入力するアドレスバーやブックマーク機能、戻るボタン、進むボタン、更新ボタンといったユーザーが普段 利⽤しているWEBブラウザのユーザーインターフェイスは含まれていません。

■ JavaScript エンジン JavaScript エンジンとは、JavaScriptの実⾏環境を提供するソフトウェアコンポーネントです。ブラウザの拡張機能を実行するためにも使用されます。また、JavaScriptエンジンの1つであるV8は、Node.js でも利用されています。

各ブラウザのレンダリングエンジンとJavaScript エンジンのまとめは以下になります。(こちらは本書出版時と変更があったみたいなので独自に最新版に更新しています。)


ブラウザ レンダリングエンジン JavaScript エンジン
Google Chrome Blink V8
Internet Explorer Trident Chakra
Microsoft Edge Blink V8
Mozilla Firefox Gecko SpiderMonkey
Safari Webkit Nitro(JavaScriptCore)

また、IOSでは事情が異なります。というのも、iOSではWebkit以外のレンダリングエンジンを使用する事が禁止されているようです。そのため、IOS(iPhone、iPad)で動くすべてのブラウザはレンダリングエンジンにWebkitを使用しています。

ブラウザレンダリングの流れ

ブラウザのアドレスバーにURLを打ち込んで、ウェブページが表示されるまでにどういった処理をするのかといったことを例に説明します。
レンダリングエンジンの処理は大きく分けて以下の4つに分類されます。
Loading
  ↓
Scripting
  ↓
Rendering
  ↓
Painting

これら4つの工程をまとめてフレームと呼びます。フレームの処理を更に詳細に分類すると以下のようになります。


リソースの読み込み -Loading

まず行われるのがリソースの読み込み(Loading)です。 ここでは、HTML,CSS、JavaScriptファイル、JPEG,PNGなどのリソースをダウンロードし、パース(解析)を行います。ダウンロードされたHTMLとCSSはそれぞれ、DOMツリーやCSSOMツリーに変換されます。

JavaScriptの読み込み -Scripting

JavaScriptの実行を行います。
こちら詳しくは以前のブログ記事で解説しているのでそちらを見ていただけると嬉しいです。
How to work JavaScript engine

レイアウトツリーの構築 -Rendering

JavaScriptの実行が終わるとレイアウトツリーの構築(Rendering)が行われます。Rendering ではスタイルの計算(Calculate Style)とレイアウト(Layout)の2つの処理が行われます。

■ Calculate Style
Calculate Style では、CSSOMツリーをすべて参照して、CSSルールのCSSセレクタのマッチング処理が行われます。つまり、DOM要素に対してどのようなCSSプロパティが当たるのかを判断します。

■ Layout
Calculate Style でDOM要素に対してどのようなCSSプロパティが当たるのかの計算が終わると、レンダリングエンジンはDOMツリー内のすべてのノードの視覚的なレイアウト情報の計算を行います。
レイアウト情報とは以下のような情報を指します。

  • 要素の大きさ
  • 要素のマージン
  • 要素のパディング
  • 要素の位置
  • 要素のz軸の位置

レンダリング結果の描画 -Painting

レンダリングエンジンの処理は最後にレンダリング結果の描画(Painting)に移行します。このフェーズでやっとユーザが見ることができる実際のピクセルを描画します。このフェーズでは、ペイント(Paint)とラスタライズ(Rasterrize)、レイヤーの合成(Composite Layers)の3つの処理を行います。

■ Paint
Paint では、内部の低レベルな2Dグラフィックエンジン向けの命令を⽣成します。RenderTree をもとに Display List と呼ばれる内部の低レベルグラフィックエンジンのための命令の列を生成します。
組み込まれるグラフィックエンジンは、ブラウザの実装毎に異なります。

■ Rasterize
Rasterizeで、⽣成された命令を⽤いて実際にピクセル(ビットマップ)へと描画します。このとき、レイヤーという単位で1枚1枚描画されます。レイヤーは、オーバーラップして表示されるコンテンツが有る場合に生成されます。レイヤーはz軸上の上下関係を持ち、複数のレイヤーが1枚の絵に合成されるとき、このz軸上の上下関係が考慮されて合成されます。
レイヤーが生成されるのは、要素が以下のような条件に当てはまるときです。

  • position: absolute; が適用されている
  • position: fixed; が適用されている
  • translate: translate3d(0 0 0)などの、GPUで描画合成されるCSSプロパティを持っている
  • opacity プロパティが適用されていて、透過して背後のコンテンツが描画される必要がある

このようにレイヤー単位でピクセルを作成するのは、再レンダリング時に変更のないレイヤーを再利用することができるためです。

■ Composite Layers
Composite Layers では、Rasterize でピクセル化したレイヤーを合成して最終的なレンダリング結果を生成します。

再レンダリング

初期読み込みが終わって描画し終わってからも、ユーザーやブラウザの何らかのアクションやJavaScriptのコードの実行やドキュメント内のイベントによってレンダリングは再度引き起こされます。これを 再レンダリングと呼びます。
しかし、再レンダリングといっても、毎回すべてのレンダリング処理が行われるわけではありません。レンダリングエンジンは、過去の描画で構築した内部表現のオブジェクトをなるべく再利用しようとします。そのため、どのようなJavaScriptのコードを書くと、どのレンダリングフェーズが引き起こされるかを把握しておくことでパフォーマンス的に優れたコードを書くことができます。

チューニングの基礎

ここではチューニングを行う上での心構えと、パフォーマンスにおいて目指すべき指標についての内容が説明されていました。

推測するな、計測せよ

パフォーマンスチューニングで重要なのはまず計測することです。
これはこの言葉の通り、効果的なチューニングのためには、まず計測し、パフォーマンス改善のために最も効果的な施策は何なのか検討していくことです。もし計測を怠り、推測でパフォーマンス改善を試みようとすると、それほど問題ではない場所をチューニングしてしまい、結果としてレンダリング全体のパフォーマンスの問題はそのままで、むしろ開発者の時間的リソース、コードの保守性や拡張性が失われただけということになる危険性があるためです。

そのため、パフォーマンス改善を正しく行うためには、レンダリングの一連の処理の中で最も多くの時間を消費している箇所、つまり、ボトルネックを適切に判断する必要があります。

目指すべき指標を設定する

ボトルネックの特定と解消だけでなく、指標を設定することも重要です。本書では、RAIL という指標を紹介されていました。Google社の開発者である Ilya Grigorik 氏が提唱したパフォーマンスモデルです。
RAIL は、Response、Animation、Idle、Load の頭文字を取って名付けられています。各項目についてみていきます。

■ Response: 100 ミリ秒
Response はユーザーが何らかのアクションに対してウェブページがユーザーインターフェース上の変化を引き起こして、応答するまでの時間を指します。この時間をRAILでは 100 ミリ秒内に抑えるべきだとしています。
例えば、ユーザーがボタンを押し、JavaScriptが実行されてダイアログが表示されるような動きの場合、ユーザーがボタンをクリックしてからダイアログが表示されるまでの時間が Response の時間となります。
もし、100ミリ秒以下で収まらない場合は処理中を意味するローディング画面のようなを表示することでユーザーに対して応答しているということを表現してから処理を行うことが求められます。
また、Response には例外があり、指のタッチやスクロールという入力に対しては、Animation と同様に 16 ミリ秒以下に抑えるべきとしています。タッチやスクロールといったイベントは、他のイベントと違って 100 ミリ秒以下の高い頻度で起こることが多いため、 100 ミリ秒だとユーザーにとってはストレスフルだからです。

■ Animation 16 ミリ秒
Animation はアニメーション中に連続して行われるフレームの中で1フレームの処理の時間の目安を指します。 フレームとは、Scripting、Rendering、Painting という描画の処理が始まって完了するまでの工程を指します。アニメーション処理では何度もフレームを繰り返すことになります。それを1フレーム 16 ミリ秒いかに抑える事ができれば、ディスプレイの一般的なリフレッシュレートである 60FPS を達成することができます。
アニメーションを実装するにあたって、フレームの中でJavaScriptを動作させる場合はJavaScriptの処理時間は 6 ミリ秒以下に抑えることを Chromium チームの開発者は推奨しています。

■ Idle: 50 ミリ秒
Idle は、アイドル状態に実行されるJavaScriptの処理時間のことです。アイドル状態とは、一度 Response や Animation、Load が終了してから何らかのユーザーのアクションを待っている状態を指します。 モダンブラウザのレンダリングエンジンでは、JavaScriptの処理とユーザーからのアクションの受け取りは同一のメインスレッドで実行されます。このためウェブページがなんの処理も行ってないように見えてもJavaScriptが実行されている最中は、ユーザーからの入力の受け取りが、JavaScriptの実行が終わるまで遅延することとなります。ここでいうJavaScriptの実行とは、おそらく、画像を遅延ロードしたり、追加コンテンツのためのfetch処理などです。

■ Load: 1000 ミリ秒
Load はウェブページのコンテンツの読み込みにかかる時間のことです。これを 1000 ミリ秒いかに抑えるべきであるとしています。しかし、様々なリソースのあるウェブページの読み込みを1000ミリ秒に抑えるのは難しいため、キャッシュやローディング画面を表示して各種リソースの読み込みを遅延させることが求められる場合もあります。

まとめ

今回は疲れたのでここまでにします。具体的なテクニックで気になったところはまた気が向いたら記事にしたいとおもいます。今回はこの本の前提部分を記事にしてみたところで終わりにします。それでは!


関連記事

この記事のハッシュタグ から関連する記事を表示しています。

ブラウザキャッシュについての備忘録

最新記事

カテゴリー

アーカイブ

ハッシュタグ