Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack Benchmarkでの ViewのInflateパフォーマンスの可視化と改善 / The Jetpack Benchmark. Visualizing and improving View Inflate performance

Jetpack Benchmarkでの ViewのInflateパフォーマンスの可視化と改善 / The Jetpack Benchmark. Visualizing and improving View Inflate performance

■ZOZOGLASSについて
https://zozo.jp/zozoglass/

■Android Profiler でアプリのパフォーマンスを測定する
https://developer.android.com/studio/profile/android-profiler?hl=ja

■Fighting regressions with Benchmarks in CI
https://medium.com/androiddevelopers/fighting-regressions-with-benchmarks-in-ci-6ea9a14b5c71

■Jetpack Benchmark アプリコードのベンチマークを行う
https://developer.android.com/studio/profile/benchmark?hl=ja

■レイアウトパフォーマンスを改善する
https://developer.android.com/training/improving-layouts

■ZOZOテクノロジーズ エンジニア【Android】 の求人一覧
https://hrmos.co/pages/zozotech/jobs?category=1479046501883478016

Dbab5e5a1fd54531e1119ede6b3b9e65?s=128

Ryosuke Horie

April 20, 2021
Tweet

Transcript

  1. Jetpack Benchmarkでの
 ViewのInflateパフォーマンスの可視化と改善
 2021/4/20(Tue) 
 Android Test Online #1
 株式会社ZOZOテクノロジーズ


    ZOZOTOWN本部 ZOZOアプリ部 Androidチーム Tech Lead
 堀江 亮介 Copyright © ZOZO Technologies, Inc.
  2. © ZOZO Technologies, Inc. 株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム
 Tech Lead

    堀江 亮介
 • 自動化とビールが好き • @Horie1024 
 2
  3. © ZOZO Technologies, Inc. https://zozo.jp/
 • 日本最大級のファッション通販サイト
 • 1,400以上のショップ、8,100以上のブランドの取り扱い(ともに2020年12 月末時点)


    • 常時83万点以上の商品アイテム数と毎日平均3,000点以上の新着 商 品を掲載
 • 即日配送サービス
 • ギフトラッピングサービス
 • ツケ払い など
 3
  4. © ZOZO Technologies, Inc. https://zozo.jp/
 • 日本最大級のファッション通販サイト
 • 1,400以上のショップ、8,100以上のブランドの取り扱い(ともに2020年12 月末時点)


    • 常時83万点以上の商品アイテム数と毎日平均3,000点以上の新着 商 品を掲載
 • 即日配送サービス
 • ギフトラッピングサービス
 • ツケ払い など
 4
  5. © ZOZO Technologies, Inc. 5 • リニューアルで発生したスクロールパフォーマンスの問題
 • ボトルネックの確認と改善に使用した方法
 ◦

    Android Profilerでのボトルネック確認
 ◦ Jetpack BenchmarkでのInflateのパフォーマンス計測・改善
 今日話すこと

  6. © ZOZO Technologies, Inc. 6 • ZOZOGLASSについて
 • もし話を聞きたい方いましたら堀江にDMください🙏
 今日話さないこと


  7. © ZOZO Technologies, Inc. スクロールパフォーマンス問題
 7

  8. © ZOZO Technologies, Inc. 8 • ホーム画面のリニューアル
 • 縦×横でNestedなRecyclerView構成
 •

    縦スクロールのカクツキ問題が発生
 • 改善するにはどうすれば良いか?

  9. © ZOZO Technologies, Inc. 9 “推測するな計測せよ”


  10. © ZOZO Technologies, Inc. 10 Android Profiler
 
 
 •

    Android Studio 3.0以上で提供されるAndroid Monitorの後継
 • 各種プロファイリングが可能
 ◦ https://developer.android.com/studio/profile/android-profiler?hl=ja
 • Android Profilerでパフォーマンス上のボトルネックを探る

  11. © ZOZO Technologies, Inc. 11 • Trace System Callsでトレースを記録
 


    CPU Profilerでの検査

  12. © ZOZO Technologies, Inc. 12 • トレース記録の検証
 
 CPU Profilerでの検査


  13. © ZOZO Technologies, Inc. 13 トレース記録からのボトルネックの発見


  14. © ZOZO Technologies, Inc. 14 トレース記録からのボトルネックの発見


  15. © ZOZO Technologies, Inc. 15 トレース記録からのボトルネックの発見
 Framesの赤 16ms以内でのレンダリングが完了しない
 フレームのドロップまたは遅延(ジャンク)が発生

  16. © ZOZO Technologies, Inc. 16 トレース記録からのボトルネックの発見


  17. © ZOZO Technologies, Inc. 17 トレース記録からのボトルネックの発見


  18. © ZOZO Technologies, Inc. 18 トレース記録からのボトルネックの発見
 • ネストされたRecyclerViewでの商品表示
 • スクロール時にジャンクが発生


    • 商品CellのInflateコストをボトルネックと判断
 

  19. © ZOZO Technologies, Inc. 19 • Inflateコスト高がスクロールのカクツキ(ジャンク)の一要因
 • 商品CellについてInflateコストを下げる改善を実施
 •

    パフォーマンスを数値として客観的に確認でき、
 簡単に繰り返し実行できる手段があると便利
 ボトルネックの解消

  20. © ZOZO Technologies, Inc. 20 • Inflateコスト高がスクロールのカクツキ(ジャンク)の一要因
 • 商品CellについてInflateコストを下げる改善を実施
 •

    パフォーマンスを数値として客観的に確認でき、
 簡単に繰り返し実行できる手段があると便利
 → Jetpack Benchmarkが利用できる
 ボトルネックの解消

  21. © ZOZO Technologies, Inc. 21 • Instrumented testsと同様にBenchmarkのコードを書ける
 • Android

    Studioからいつでも実行可能
 • CIのフローに組み込みことも可能
 ◦ 参考: Fighting regressions with Benchmarks in CI
 Jetpack Benchmark

  22. © ZOZO Technologies, Inc. 22 • Benchmark Moduleを作成することでベンチマークを実行可能
 Jetpack Benchmarkでのベンチマーク実行


  23. © ZOZO Technologies, Inc. 23 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  24. © ZOZO Technologies, Inc. 24 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  25. © ZOZO Technologies, Inc. 25 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  26. © ZOZO Technologies, Inc. 26 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  27. © ZOZO Technologies, Inc. 27 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  28. © ZOZO Technologies, Inc. 28 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  29. © ZOZO Technologies, Inc. 29 @RunWith(AndroidJUnit4::class) class ViewInflateBenchmark { @get:Rule

    val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun inflate() { val context = ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.AppTheme) val inflater = LayoutInflater.from(context) val root = FrameLayout(context) benchmarkRule.measureRepeated { inflater.inflate(R.layout.view_item, root, false) } } }
  30. © ZOZO Technologies, Inc. 30 商品CellのInflateパフォーマンスの改善
 
 • 商品CellのInflateコストを削減
 •

    スクロール時にジャンクを低減させる
 • ベンチマーク結果を確認しながら改善
 

  31. © ZOZO Technologies, Inc. 31 • 方針
 ◦ レイアウト構造のflatten化
 ◦

    レイアウトの遅延読み込み
 • Android Developersの「レイアウトパフォーマンスを改善する」
 の内容が基本
 ◦ https://developer.android.com/training/improving-layouts
 
 商品CellのInflateパフォーマンスの改善

  32. © ZOZO Technologies, Inc. 32 • 2種類のレイアウトの遅延読み込み方法
 ◦ ViewStub
 ◦

    AsyncLayoutInflater
 
 レイアウトの遅延読み込み

  33. © ZOZO Technologies, Inc. 33 • 軽量なView
 • ViewStub自身は何も描画しない
 •

    layout属性で指定したレイアウトに任意のタイミングで置き換え
 
 ViewStub
 <ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" />
  34. © ZOZO Technologies, Inc. 34 • 軽量なView
 • ViewStub自身は何も描画しない
 •

    layout属性で指定したレイアウトに任意のタイミングで置き換え
 
 ViewStub
 val view: View = findViewById<ViewStub>(R.id.stub_import).inflate()
  35. © ZOZO Technologies, Inc. 35 • レイアウトを非同期でInflate
 • ViewStub同様任意のタイミングでInflateを行える
 •

    Inflateした結果のViewをcallbackで受け取る
 
 AsyncLayoutInflater
 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val content = findViewById<ViewGroup>(android.R.id.content) AsyncLayoutInflater(this) .inflate(R.layout.activity_main, content) { view, _, _ -> setContentView(view) } } }
  36. © ZOZO Technologies, Inc. 36 ViewStub vs. AsyncLayoutInflater
 • ViewStubは同期的、AsyncLayoutInflaterは非同期的にInflate


    • 頻繁に使われるViewの置き換えにViewStubを使用
 ◦ 同期的にInflateが行われるためジャンクが解消されない
 ◦ 使用されないViewであればViewStubを使って問題ない
 ◦ 例: onBindViewHolderで同期的にInflateが起きている
 
 
 

  37. © ZOZO Technologies, Inc. 37 • 商品Cellから次の特徴を持つViewを遅延読み込み
 ◦ ネストが深い構造を持ったView
 ◦

    ホーム画面で使用されないView
 ▪ 商品Cellは複数のリストで共有されている
 ◦ Inflateのコストが高いView
 ▪ Profilerの結果から確認
 
 AsyncLayoutInflaterでのViewの遅延読み込み

  38. © ZOZO Technologies, Inc. 38 • 分析ペインのcallstack表示からInflateコストが高いViewを確認
 AsyncLayoutInflaterでのViewの遅延読み込み


  39. © ZOZO Technologies, Inc. Inflateコストの改善結果
 39

  40. © ZOZO Technologies, Inc. 40 • 12.31msから1.32msに短縮
 
 改善前後のBenchmarkでの計測結果


  41. © ZOZO Technologies, Inc. 41 • Profilerの結果では改善はされているが劇的に良くなった訳ではない
 • RecyclerView自体のパフォーマンスチューニングも実施
 ◦

    チーム内での検証では体感でも改善された
 実際のスクロールパフォーマンスは改善されたのか?

  42. © ZOZO Technologies, Inc. 42 • Profilerの結果では改善はされているが劇的に良くなった訳ではない
 • RecyclerView自体のパフォーマンスチューニングも実施
 ◦

    チーム内での検証では体感でも改善された
 • 今後
 ◦ 改善された状態で近日中にリリース(4/20時点で未リリース🙇)
 ◦ 仕様検討まで含めて商品Cellの最適化を進める
 ◦ Benchmarkの対象をInflateのではなくRecyclerViewとする
 実際のスクロールパフォーマンスは改善されたのか?

  43. © ZOZO Technologies, Inc. まとめ
 43

  44. © ZOZO Technologies, Inc. 44 • Benchmarkベースで商品CellのInflateパフォーマンス改善をした
 • だがスコアほど実際のボトルネックは解消されていない
 •

    RecyclerViewのスクロールそのもののBenchmarkを取る等を行いよりパ フォーマンスを改善できる方法を探る
 まとめ

  45. © ZOZO Technologies, Inc. 45 • ZOZOテクノロジーズではAndroidエンジニアを募集しています!
 • 少しでもご興味のある方気軽にお声かけください。カジュアル面談をぜひ お願いします!


    最後に

  46. None