はじめに
こんにちは。TIGの藤田です。
Dart/Flutter連載 の2日目として、VSCodeのDevToolsを使用したWidget Build
の可視化についてご紹介します。
Flutterアプリの開発では、ウィジェットのビルド単位を考えてコードを記述/改修すると思います。
AndroidStudioのPerformance機能を使ってウィジェットのリビルドを確認している例は見かけるのですが、VSCodeでの確認方法を見かけなかったため調べてみました。予想以上に高機能で、今回使わなかった機能も含めて活用どころがありそうです。
内容
1. VSCode Dart DevTools
2. Widget Buildをタイムラインで確認する
3. 実装のWidget Buildへの影響を確認
VSCode Dart DevTools
Flutter公式のDevToolsは、VSCodeのDart Extension, Flutter Extensionのインストールと共にインストールされます。レイアウト構造を可視化/編集できるFlutter Inspector がよく使われると思いますが、他にもCPUやメモリ、Networkの可視化など多機能です。今回は、Performance view機能を使ってWidget Build
をタイムラインで確認してみます。
Widget Build
をタイムラインで確認する
- devTools起動: 公式手順に従って、アプリの起動後にDevToolsを起動します。
- DevToolsのPerformanceタブを開きます。
- 「Enhance Tracing」から、Widget Builds, Layouts, PaintsをTrackするように設定します。
- アプリを実行すると、タイムラインにFrameごとの処理時間が表示されます(#1)。Frame Time(UI)は、Dart VM内でビルドされるLayer treeと描画コマンドを含む軽量オブジェクトの作成時間を表しています。これらオブジェクトがGPUに渡されることでレンダリングが行われ、その実行時間が、Frame Time(Raster)になります。
- バーグラフをクリックすると、UIイベント, Raster(GPU)イベントそれぞれの内訳を確認することができます。UIイベントは、実装Dartコードを直接反映していて、Widgetレベルで実行イベントを確認できます。(#2)
- Raster(GPU)イベント(#3)は、UIイベントから作成されます。アプリのパフォーマンスを考える上では、UIグラフに課題がなくても、GPUグラフに課題があることもあります。
- 「Performance Overlay」ボタン(#4)をONにすると、アプリ画面に重ねる形で、UIグラフとGPUグラフを確認できます。
【補足】 公式ページに紹介されるパフォーマンス診断では、UIスレッドとGPUスレッドのプロファイルから実装に落とし込んで対処することを説明しており、実機を使用したprofile modeにて行うことを前提としています。今回はiOSシミュレータにて、DevToolsの使い方と、ソースコードがプロファイルに与える影響の確認方法を見てみたいと思います。
実装のWidget Build
への影響を確認
例として、アニメーションの実装方法によるWidget Build
パターンの違いをタイムラインで確認します。今回はiOSシミュレータ(iPhone 13)を使用しています。
1) 全体ビルド(アンチパターン)。
bodyのアニメーションのためにsetState()することで、レイアウト全体をビルドしてしまっています。
import 'package:flutter/material.dart'; |
画面はこのようになります。Overlayされたグラフの上段がRaster(GPU)スレッド, 下段がUIスレッドを表しています。16msおきに補助ラインが引かれていますが、おおよそ16msを超えるFrameは描画されずにJankとなります。UIスレッド側に多くのJankが見られることから、この実装には課題がありそうだと分かります。
Frame実行時間のタイムラインを見ても、UIグラフに赤色のJank(slow frame)が多くなっています。
UIイベントの内訳を見てみましょう。連続する2Frameをクローズアップしていますが、アニメーションには関係のないAppBarやFloatingActionButtonも、Frame毎にビルドしてしまっていることが分かります。今回はビルド対象が小さいですが、対象が大きければ更にコストがかかりそうです。
GPUイベントも確認してみます。こちらは、赤いグラフが見られなかったことからも大きな課題はなさそうです。
2) コードの改善
Frame毎のビルド範囲をアニメーション部分に限定するにはAnimatedBuilder等を用いる方法があります。ただし今回のケースは、以下のようにImage ウィジェットを使用することで、Frame毎のビルドをなくすことができます。
import 'package:flutter/material.dart'; |
アプリ画面は以下になります。下段UIスレッドから、Jankがほぼなくなりました。少し見にくいですが、各グラフに平均実行時間が表示されていて、GPUスレッドは5.4ms/frame, UIスレッドは7.9ms/frameとなっています(改修前は、GPUスレッドが4.1ms/frame, UIスレッドが19.7ms/frameでした)。。
Frame実行時間のタイムラインを見ても、UIグラフに赤色のJank(slow frame)が見られません。平均43FPSとなっており、改修前の28FPSより改善しています。
UIイベントの内訳を見てみると、Frame毎の「Build」処理自体がなくなっていることが分かります。
GPUイベントについては、画面Overlayグラフからもわかるように、改修前より少し実行時間が増えていますが、Jankは見られず課題はなさそうです。
まとめ
- VSCodeのDevToolsを使って、Dart VM上のDartコード実行によるビルド(UIスレッド)と、GPU上のレンダリング(Rasterスレッド)のFrame毎の実行時間をタイムラインで可視化できます。
- Jank Frameを1つの指標として、UIスレッド(Dartコード)の内訳を確認することで、実装コードの改善に利用できます。
- 効果的なパフォーマンス改善には、他の観点も必要となります。
- I/O処理(I/Oスレッド)は、パフォーマンス上コストが高くUIスレッドやGPUスレッドをブロックするため、その考慮が必要。
- CPUやメモリメトリクスの考慮(DevToolsのうち、今回取り上げていない機能)
- 実機(ユーザーが使用し得る一番遅いデバイス)での確認
- パフォーマンス改善については、公式ページも参考に、今回紹介できなかった機能も活用していきたいところです。
参考リンク
- Improving rendering performance
- Flutter公式サイトにおける、レンダリングパフォーマンスのページ。