ページ上でずっと動いているsetTimeout、setInterval、requestAnimationFrameを見つけてパフォーマンス改善する
複雑なウェブアプリケーションになってくると、1つのページで複数のTimerなどを回すことがあります。
例えば、Twitterのようなアプリならば、ポーリングで更新するためにsetInverval
のようなタイマーを回します。
また、ゲームなどCanvasで描画を行うアプリケーションならば、メインループをrequestAnimationFrame
で回します。
このように色々なタイマー系がありますが、アプリが多機能になっていくと色々なタイマーが同時に動くようになっていきます。 特に問題がなりやすいのが表示中だけタイマーを回すコンポーネントです。
よくあるのが次のようなmount時にtimerを開始して、unmount時にtimerを停止するコンポーネントです。
この実装はunmount時に止めているので問題ありませんが、componentWillUnmount
の実装を忘れるとそのタイマーはコンポーネントが消えた後も回り続けます。
export class TimerComponent extends React.Component {
componentDidMount() {
this.startTimer();
}
componentWillUnmount() {
this.stopTimer(); // <= これを止め忘れるとTimerリーク
}
}
このような意図しないで動いてるタイマーなどを見つけるspyスクリプトを書きました
使い方
- 次のスクリプトをページに読み込ませる
- コンソールにコピペして実行しても大丈夫
- Non strict modeじゃないと動かないことやってるで混ぜる場合は注意
- 結果を取りたくなったら
window.getContexualLogResult()
を叩く
“setTimeout”, “setInterval”, “requestAnimationFrame”の実行元の関数毎に呼ばれた回数をまとめて表示してくれます。 またスタックトレースも無理やり入れているので、意図しない呼び出しが頻発しているならその部分のコードを直す目安となります。
例えば、twtter.comでこれを実行してみるとsetInterval
とrequestAnimationFrame
が回っていることが分かります。
これは定期的な更新をするために呼び出していることがわかります。
タイムラインツールでも記録はできるのですが、呼び出し元毎のグルーピングやフィルタリングが難しいです。(良い方法があるなら知りたい) “setTimeout”, “setInterval”, “requestAnimationFrame”を乗っ取ってログを取ることで実装しています。
一回のタイマー発火ごとの処理は小さくても、スペック弱いデバイスではネックとなることがあるのでそのような無駄な処理を発見する目的で作りました。 (ChromeのCPU Throttlingなどでシミュレートすると問題を見つけやすいです)
最近は、分かりやすい指標が既にある起動時間のパフォーマンスではなく、アプリを起動後のパフォーマンスを改善しています。
次の記事で作ってたものはそういうところを改善する目安を探すためのツールです。
- performance.markにメタデータを紐付けできるライブラリを書いた | Web Scratch
performance.mark
with metadata is useful for Real user monitoring
これのFB Flux版を実装してたけど、やっぱりこういうの必要だと思う。ボトルネックが可視化されるので特殊な技能がなくてもパフォーマンス悪いところが発見できる。 "Almin + React/Vue.jsのパフォーマンスプロフ…" https://t.co/9alBY5tnca
— azu (@azu_re) November 24, 2017
アプリの起動後の指標として、何かした時に反応が100ms以内、アニメーションが10ms以内、アイドル時の処理は50ms以上以内のブロックにする(long task)、Loadは1000ms以内などを指標を定めたRAILモデルなどがあります。
これらはマイクロなベンチマークを取ってからそれを改善していくという積み重ねをしています。 この記事で書いたspyスクリプトも、無駄に動くタイマーが減ればその分処理が減ったということが明確であるため、それを検出するために作りました。
また、アプリ起動後は何もしてないときも体感が良いということも必要になります。 例えばユーザー操作がないけど、タイムラインがスムーズに更新される、映像がスムーズに流れる、放置ゲームを眺めててつっかかりがないとか、リアルタイムにデータを受信してて止まらないなどがこれにあたります。
これらの放置時の更新は大体裏では”setTimeout”, “setInterval”, “requestAnimationFrame”などを使っていることが多いです。 (WebRTCやWebSocketなどもありますが、それらが止まってないかを定期的にチェックする仕組みなどにも関係します)
タイマー系は意図しないタイミングで他の処理と重なるとUIを固めたりするので、requestIdleCallbackと組み合わせるなどの工夫が必要になるかもしれません。
その他
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。