パフォーマンスの計測の仕方

計測できないなら安易に直さない

パフォーマンスの計測の仕方はさまざまです。 計測方法はボトルネックとなる部分(ネットワークやレンダリングといった箇所)によって異なります。

計測方法も大事ですが、計測する環境も重要です。

スロットリング

開発に使うようなマシンはハイスペックです。 パフォーマンスの計測を行う際は、必ずCPUスロットリングなどのスロットリングを行った状態で一度計測するべきです。

  • CPUスロットリングを行う
    • 6x 6倍遅い状態
  • ネットワークスロットリングを行う

実際に利用した設定

この視聴中のパフォーマンス改善では次のような設定を利用して計測しています。 ネットワークスロットリングについてはランタイムではあまり関係ないものが多かったため特に設定していませんでした。

  • 値の計測: Chrome
    • CPUスロットリング: 6x
    • ネットワークスロットリング: なし
  • 動作の確認: IE/Edge/Firefox/Chrome/Safari

参考

また、Chromeのネットワークスロットリングはリクエストレベルのスロットリングになります。そのためパケットレベルのスロットリングをする場合は別のツールを使ったほうが安定します。

実機確認

映像のデコードなどハードウェアの機能を使うものほど実機で確認するべきです。 VMのWindowsでは再現しないが、実機では再現するといった違いなどが発生します。

計測ツール

User Timing API

User Timing APIと呼ばれるものにはperformance.markperformance.measuresなどがあります。 これらは、自分で指定した範囲をマーキングできます。 閲覧中(視聴中)のパフォーマンスというのは、機械的に取ることが難しいです。

そのため、performance.markperformance.measuresを使ってアプリケーションに適した「指定範囲」を作ることがパフォーマンスの計測に役立ちます。

指標で作成したパフォーマンスログもこれらのAPIを使っています。

また、最近はFluxやReduxなどイベントを発火してViewを更新するスタイルを取っていることが多いと思います。 イベントが起点となっているときの利点として、何のイベントによって表示が更新されたかという関連付けが簡単なところにあります。

生放送の視聴ページではfluxReactが使われていました。

この2つならイベント(FluxのDispatch)と表示の更新(Reactのrender)を関連付けて可視化することは簡単です。 VueやAlminなど最近のライブラリなら大抵何とかなります。

イベントと更新の可視化

fluxのイベントとReactの更新の関係をUser Timing APIを使って可視化してみましょう。

といっても難しい話ではなく、Fluxがdispatchしたタイミングでperformance.markを貼り、Storeが更新されたタイミングもう一度performance.markを貼るだけです。 Reactは?react_perf(React 16からはデフォルト)を付けるだけで、自動的にperformance.markをつけてくれます。

FluxのDispatchの実装は次のような形にしました。

Dispatcherのシングルトンを作るときにUser Timingの処理を追加

import * as flux from "flux";
/**
 * アクションを発行した際に受け渡す情報.
 */
export interface ActionPayload {
    type: string;
}

/**
 * アクションを発行するもの.
 */
export const Dispatcher = new flux.Dispatcher<ActionPayload>();

/**
 * 開発向けのFluxのDispatchロガー
 *
 * - Fluxでdispatchしたアクションをコンソールへ出力
 * - タイムラインツールにアクションの実行を記録
 *  - 正確な実行時間は記録できないのであくまで参考
 *  - 実行時間はdispatchしてから次のrequestFrameAnimationが起きるまでの時間が記録されている
 *
 * URLに`?react_perfを付けることでActionとViewの処理の関係をタイムラインツールでみることができる
 *
 * - https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool?hl=ja
 * - https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab
 *
 * 無効化する方法:
 *
 * 以下の環境変数と共に起動することで無効化される(production時は自動で無効化される)
 *
 * DISABLE_FLUX_LOG=1 npm start
 *
 */
if (process.env.NODE_ENV !== "production" && process.env.DISABLE_FLUX_LOG !== "1") {
    let requestAnimationFrameId: number | null = null;
    // 同期的に複数のPayloadが来ることがあるので貯める
    let currentPendingPayloads: ActionPayload[] = [];
    Dispatcher.register((payload: ActionPayload) => {
        // 1. performance.markの計測開始
        performance.mark(payload.type);
        if (requestAnimationFrameId) {
            cancelAnimationFrame(requestAnimationFrameId);
        }
        currentPendingPayloads.push(payload);
        // 現在はStoreのDispatcherTokenを取る方法がないため、非同期でちょっとだけ待つことで変化したStore一覧を取得している
        requestAnimationFrameId = requestAnimationFrame(() => {
            currentPendingPayloads.forEach(payload => {
                const type = payload.type;
                // 2. performance.markの計測終了
                const measureMark = `${type} [Action]`;
                performance.measure(
                    measureMark,
                    payload.type
                );
                // 計測に使ったmarkは削除する
                performance.clearMarks(type);
                performance.clearMeasures(measureMark);
            });
            currentPendingPayloads = [];
            requestAnimationFrameId = null;
        });
    });
}

これで、FluxがdispatchしたらそのActionの名前がChromeのタイムラインツールで見れます。 後は、Timeline ツールで単純に記録するだけで、「どのアクション」によって「どのコンポーネント」が更新されているかが可視化できます。

timeline.png

問題の糸口を探すには情報の可視化が大切です。

情報量の削減

Chromeの開発者ツールは情報量が多いため、逆に何が重要なのかがわからなくなる問題もあります。 そのような場合は、フィルタリングを工夫するか必要な情報だけを出すログツールを自分で書くのが簡単です。

大げさな仕組み化をしなければ既存の機能を乗っ取ったりしてログを得ることはできるはず。 この際に、エコシステムが大きなライブラリはそのようなツールもすでにあることが多いという利点があります。

Reactではさまざまなツールがありますが、必要に応じて使い分けることが大切です。

CPUプロファイラを自作する例

Video要素やhls.jsのイベントを調べるツール

results matching ""

    No results matching ""