every Tech Blog

株式会社エブリーのTech Blogです。

Flutter のパフォーマンス改善事例紹介

はじめに

こんにちは。DELISH KITCHEN 開発部で小売向き合いの開発をしている池です。

弊社では小売事業者が自社ネットスーパーアプリをスピード導入できるプラットフォームを提供しています。

https://biz.delishkitchen.tv/retailhub

ネットスーパーアプリの開発に Flutter を利用しており、日々事業拡大に向けて開発を進めています。

本記事では、最近開発を進めている中で下記 2 つのパフォーマンス改善に繋がった事例について、DevTools の Performance View を交えながらご紹介します。

  • Flutter 3.10 アップデートに伴う Shader compilation jank 解消
  • GridView のパフォーマンス改善

パフォーマンス改善の前提知識

DevTools Performance View

Flutter には DevTools というパフォーマンスおよびデバッグツールが標準で備わっており、その中の一つの機能に Performance View という機能があります。この機能は、フレームごとのパフォーマンスメトリクスをキャプチャして視覚化する機能です。この機能を利用することで後述するジャンクという低パフォーマンスなフレームとそれに関連する処理を検出することができます。

フレームチャート

フレームチャートでは、フレームごとの描画の処理時間が表示されます。色のついたバーの各セットは 1 フレームの処理時間表しており、色によって UI 処理時間、Raster(GPU)処理時間 等を表しています。

タイムラインイベントチャート

タイムラインイベントチャートでは、フレームチャートでバーを選択すると、対応するフレームの詳細が表示されます。UI スレッドと Raster(GPU)スレッドの両方のフレームを構築した全てのイベントを確認することができます。

ジャンクとは

画面の描画について、1 フレームを描画するのに 1 フレーム以上の描画時間がかかってしまうと、該当のフレームは描画されずスキップされます。その場合、アプリ画面はコマ飛びのようにガタついた表示となります。このような低パフォーマンスなフレームをジャンクといいます。 1 フレームは、60fps デバイスだと 1/60 ≒ 16 ミリ秒なので、各フレームの描画時間が 16 ms 以下でなければ、UI が乱れたり、フレームが落ちたりすることがあります。

そのようなジャンクフレームは Performance View のフレームチャートではオレンジ色として表示されるため、フレームチャートを利用することで検出することができます。上記フレームチャート画像をみると、オレンジ色になっているバーが確認できると思います。

Flutter 3.10 アップデートに伴う Shader compilation jank 解消

弊社ネットスーパーアプリでは現在 Flutter 3.10 へのアップデート対応を進めています。Flutter 3.10 では Impeller という新しいレンダリングエンジンが iOS のデフォルトのエンジンとして設定されました。Impeller により 後述の Shader compilation jank という起動時のパフォーマンス低下の問題が解消されるので、その内容について Performance View を利用して確認していきます。

Shader compilation jank とは

Shader compilation jank は 前述したジャンクの一種です。 Flutter のパフォーマンス問題の一つに、初回アニメーション時に大きくガタつく問題があります。シェーダーは GPU 上で動作するコードの一部で、Flutter が従来レンダリングに使用している Skia グラフィックスライブラリは、新しい描画のコマンドを初めて見たとき、カスタム GPU シェーダを生成してコンパイルすることがあります。この Skia によるシェーダー生成とコンパイルは、フレームのワークロードと順に行われます。コンパイルにはカスタム GPU シェーダによっては数百ミリ秒かかる可能性があり、この重たいコンパイルによってフレームが欠落し、大きくガタつくような動作となります。これが Shader compilation jank です。

Shader compilation jank 解消確認

Flutter 3.3.10

バージョンアップ前の Flutter 3.3.10 で Shader compilation jank の発生を確認します。

アプリの初回アニメーションを動作させた際の Performance View が次の画像です。赤黒く表示されている箇所が Shader Compilation の処理時間となります。 4.4ms と当アプリでは影響は少ないですが、発生していることが確認できます。

Flutter 3.10.6

次に、Flutter 3.10.6 にアップデートした状態で、同様に Performance View を確認します。

赤黒く表示されていた Shader Compilation がなくなっていることが確認できました。

GridView のパフォーマンス改善

次に、GridView のパフォーマンス改善についてご紹介します。

弊社ネットスーパーアプリには、カテゴリに所属する商品をグリッド形式で表示する画面があります。無限スクロール機能により画面をスクロールダウンしていくと、次々に要素を読み込むようになっています。この画面においてパフォーマンスの低い実装となっていたため、これから説明する改善方法を試しました。

GridView とは

GridView は上記アプリ画面のようにグリッド形式で要素を表示する際に使用する Widget です。 弊社ネットスーパーアプリのように大量の要素を表示するグリッドでは GridView.builder コンストラクタを使用することが公式に推奨されています。GridView.builder コンストラクタを使用することで、画面の表示領域に必要な要素のみがビルドされ、領域外の要素は領域内に入ったタイミングで動的に描画されるような、遅延描画を実現することができます。

次のサンプルで画面をスクロールすると、console にビルドされたタイミングで文字が表示され、遅延描画されていることが確認できます。

GridView のパフォーマンスが低下する実装例

GrieView を他の Widget と一緒に縦方向に並べてスクロールさせたいケースを考えます。

この実装に Column と SingleChildScrollView を組み合わせた次のサンプルのような実装を試してみましょう。先ほどと同様に console でビルドされたタイミングを確認すると、遅延描画されずに全ての要素が同時にビルドされていることがわかります。

これは shrinkWrap が true の場合において、GridView が自身の子 Widget に合わせて高さを調整するためです。親である SingleChildScrollView と Column は高さが無制限であるため、GridView は事前に全ての子要素の高さを計算し、その高さに基づいてスクロールを設定します。そのため全ての要素が同時にビルドする必要があり、遅延描画されずパフォーマンスが低下します。また、shrinkWrap を false にすると、無制限の高さと認識され、エラーとなります。

弊社ネットスーパーアプリでも同様の実装となっており、表示領域外の要素も同時にビルドされていました。Performance View で確認すると、次の画像のように StaggeredGrid の下で大量の要素が生成されています。

GridView のパフォーマンス改善の実装例

この問題は CustomScrollView と SliverGrid を使用することで回避することができます。

このコードでは、CustomScrollView 内に SliverToBoxAdapter で ListTile を配置し、さらに SliverGrid を配置しています。SliverGrid 内では、SliverChildBuilderDelegate を使用して子要素を動的に生成しています。これにより複数の Widget を縦に並べたスクロール画面において、Grid の要素の遅延描画を実現することができました。

弊社ネットスーパーアプリの該当実装を同様に修正すると、GridView により生成される要素数が大幅に削減され、スクロールで動的に遅延描画されるようになりました。

おわりに

今回はパフォーマンス改善に繋がる事例を 2 つ、Performance View を交えながら紹介しました。

Performance View を利用することで、普段開発している中では気付きにくい低パフォーマンスフレームやその処理のボトルネックを検出することができます。

今回ご紹介した改善は大きな改善ではないですが、小さい改善の積み重ねがユーザビリティの高さに繋がると思うので、今後もパフォーマンスを意識しながら開発を続けていければと思います。

この記事が少しでも Flutter 開発の参考になれば幸いです。ありがとうございました。

参考

https://docs.flutter.dev/tools/devtools/performance

https://docs.flutter.dev/perf/shader

https://docs.flutter.dev/perf/best-practices#be-lazy

https://youtu.be/LUqDNnv_dh0?feature=shared