TIL

Did you learn anything today?

Webフロントエンドハイパフォーマンスチューニングの読書メモ

Webフロントエンド ハイパフォーマンス チューニングの読書メモ。

Webブラウザレンダリングエンジン、JavaScriptエンジンについて

ブラウザ レンダリングエンジン JavaScriptエンジン
Chromium (Chrome, Opera, 次世代 Edge) Blink V8
Safari WebKit JavaScript Coer
Firefox Gecko SpiderMonkey
Edge EdgeHTML Chakra
IE Trident Chakra

リソースの取得に用いるネットワークプロトコル

  1. URL に含まれるホスト名の解決
  2. HTTP による取得
    1. TCP 接続の確立
    2. (HTTPS であれば)TLS 接続の確立
    3. HTTP リクエストの送信と HTTP レスポンスの受け取り

IP

ネットワークのノード間のパケットのやり取りを中継するプロトコル

ホスト名の解決に DNS、リソースの取得には HTTP が用いられます。これらはどちらも IP 上で利用されます。

TCP

TCP はコネクションとい概念を持ちます。相手先の IP アドレスとポート番号を指定して接続を確立し、一度接続を確立すると接続が終了するまで、IP と比べると信頼性のあるデータ転送を行えます。

ウェブにおいて、HTTP はこの TCP の上で利用されます。したがって、HTTP でリソースを取得する前に必ず TCP 接続を確立します。

DNS

ホスト名を IP アドレスを変換する作業は、クライアント内の DNSゾルバを通じて行います。DNSゾルバは自身のキャッシュや DNS サーバに問い合わせることでホスト名に紐づく IP アドレスを検索します。


JavaScript による計測

  • Navigation Timing API による計測
  • User Timing API による計測
  • Resource Timing API による計測
  • Frame Timing APIによる計測
  • Server Timing APIによる計測
  • High Resolution Time APIによる計測
  • Perfomance Observer による継続的な計測

Navigation Timing API による計測

ブラウザがページをロードするのに要した処理時間の内訳を知ることができる。

  • window.performance.timing オブジェクト
  • window.performance.navigation オブジェクト

User Timing API による計測

開発者が任意の処理にかかる時間を計測することができる。

Resource Timing API による計測

リソースの取得にかかっている時間の統計情報を得られる。

Navigation Timing API では、ユーザーがどれぐらいブラウザのナビゲーション処理に待たされているかを詳細に知ることができる。ただし、個別のリソースの取得にどう時間がかかっているかについては分からない。

※ Network パネルで見ることができるリソース取得のパフォーマンスの詳細情報とほとんど同じもの。

Frame Timing API による計測

Frame Timing APIはページ描画のフレーム情報を取得するAPI

Navigation Timing APIResource Timing APIはネットワーク処理に関するパフォーマンスの情報だが、これはレンダリングパフォーマンスの情報ということになる。

Server Timing API による計測

Server Timing APIは、レスポンスヘッダのServer-Timingフィールドにサーバーの各処理にかかった時間を含めて、クライアントでそれを参照できるようにしようというAPI

High Resolution Time API による計測

High Resolution Time APIはマイクロ秒単位でパフォーマンスを計測するためのAPI

Date.now()のミリ秒では不十分ということで、細かな計測にはperformance.now()を使うと良い。

Perdoemance Observerによる継続的な計測

ブラウザパフォーマンスを計測するAPIは以下の5つがある。

  • Navigation Timing API
  • User Timing API
  • Resource Timing API
  • Frame Timing API
  • Server Timing API

Performance Observerはこれらの変更を監視するAPI

Javascript の実行のチューニング

  • Page Visibility API


高度なチューニング

ブラウザのレンダリングは4つの工程から成る。

① Loading -> ② Scripting -> ③ Rendering -> ④ Painting

さらに、各工程はそれぞれ以下の処理に細分化される。

  • ① Loading
    • Download
    • Parse
  • ② Scripting
  • ③ Rendering
    • CalculateStyle
    • Layout
  • ④ Painting
    • Paint
    • Rasterize
    • CompositeLayers

① Loading

省略。

② Scripting

省略。

③ Rendering

Calculate style

DOM 要素と CSS ルールを突き合わせて要素ごとのスタイル情報を計算する。

DOM ツリーの構造を変化させたり、DOM 要素の属性を変化させると Calculate Style が引き起こされる。

  1. DOM ツリーの構造の変化ってなに?
  2. appendChild()メソッドや removeChild()メソッド、innerHTML プロパティや textContent プロパティなどを使って DOM ツリーに変化を加えること。

  3. DOM 要素の属性ってなに?

  4. class 属性や id 属性などを変更すること。

アニメーション中にこういうった変更を行う理由はないので避けてください。ある要素の CSS プロパティを変化させたい場合には、 Javascript で次のようにDOM要素をstyle プロパティから直接変更することで DOM 要素と CSS ルールのマッチング処理を避けることができます。

element.style.opacity = '0.8';

Layout

視覚的要素のレイアウト情報を計算する

DOM ツリーの構造を変化させたり、コンテンツを変化させると Layout 処理が引き起こされる。
また、レイアウトの更新を必要とする CSS プロパティを変更すると、Layout 処理が引き起こされます。
Layout 処理が引き起こされると、後続の Paint 処理や Composite 処理もすべて引き起こされます。

Layout を引き起こさずに要素の位置や大きさを変えたい場合には、transform CSS プロパティの値に translate()や scale()を使ってください。

// だめ
element.style.marginTop = '10px';
element.style.left = '100px';

// よい
element.style.transform = 'translate(100px, 10px)';

④ Painting

視覚的要素をレイヤーごとにビットマップ化する。

Paint は、内部で生成されたレイヤーごとに描画命令を生成する処理です。 Paint が引き起こされると、皇族の Resterize と Composite Kayers も実行される。

Layout 処理が引き起こされた場合や、再度 Resterize が必要となる CSS プロパティを変更すると引き起こされます。

ペイントを引き起こす CSS プロパティ

  • color
  • border-color
  • backgorund-color
  • backgorund-image
  • z-index

ペイントを引き起こさない CSS プロパティ

  • opacity
  • transform

原理的にそれが難しい場合、たとえば要素の色を変更したり背景画像を動かしたりするようなアニメーションの場合はここで最適化をストップさせることになる。

Composite Layers

描画されたレイヤーを合成して最終的なレンダリング結果を生成する。

translateZ ハックを使用してレイヤーの合成に GPU を使うと最適化できる。

.target {
  transform: translateZ(0);
}

このハックを適用した上でアニメーションを行うとほとんんど GPU でのみ描画される高速なアニメーションを実装できる

var element = document.getElementByID('animation-target');

// アニメーションを始める前にGPUで合成されるレイヤーを生成する、
element.style.transform = 'translateZ(0)';

var i = 0;
var loop = function() {
  // アニメーション中にもGPUでの合成が解除されないようにtranslateZ(0)をつける。
  requestAnimationFrame(function() {
    element.style.transform = 'translateZ(0) translate2d(' + (i++) + 'px,0px');
    loop();
  });
};
loop();

これらの再レンダリング時のフェーズを減らすことは非常に重要。

参考文献

ブラウザレンダリングを理解するため簡単にまとめてみた - Qiita

パフォーマンスに関する各種ブラウザAPI - EagleLand

知っておきたいブラウザについての基礎入門 - Speaker Deck