Slide 1

Slide 1 text

Jetpack Benchmarkでの
 ViewのInflateパフォーマンスの可視化と改善
 2021/4/20(Tue) 
 Android Test Online #1
 株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム Tech Lead
 堀江 亮介 Copyright © ZOZO Technologies, Inc.

Slide 2

Slide 2 text

© ZOZO Technologies, Inc. 株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム
 Tech Lead 堀江 亮介
 ● 自動化とビールが好き ● @Horie1024 
 2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

© ZOZO Technologies, Inc. 5 ● リニューアルで発生したスクロールパフォーマンスの問題
 ● ボトルネックの確認と改善に使用した方法
 ○ Android Profilerでのボトルネック確認
 ○ Jetpack BenchmarkでのInflateのパフォーマンス計測・改善
 今日話すこと


Slide 6

Slide 6 text

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


Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

© ZOZO Technologies, Inc. 8 ● ホーム画面のリニューアル
 ● 縦×横でNestedなRecyclerView構成
 ● 縦スクロールのカクツキ問題が発生
 ● 改善するにはどうすれば良いか?


Slide 9

Slide 9 text

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


Slide 10

Slide 10 text

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


Slide 11

Slide 11 text

© ZOZO Technologies, Inc. 11 ● Trace System Callsでトレースを記録
 
 CPU Profilerでの検査


Slide 12

Slide 12 text

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


Slide 13

Slide 13 text

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


Slide 14

Slide 14 text

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


Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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


Slide 17

Slide 17 text

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


Slide 18

Slide 18 text

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


Slide 19

Slide 19 text

© ZOZO Technologies, Inc. 19 ● Inflateコスト高がスクロールのカクツキ(ジャンク)の一要因
 ● 商品CellについてInflateコストを下げる改善を実施
 ● パフォーマンスを数値として客観的に確認でき、
 簡単に繰り返し実行できる手段があると便利
 ボトルネックの解消


Slide 20

Slide 20 text

© ZOZO Technologies, Inc. 20 ● Inflateコスト高がスクロールのカクツキ(ジャンク)の一要因
 ● 商品CellについてInflateコストを下げる改善を実施
 ● パフォーマンスを数値として客観的に確認でき、
 簡単に繰り返し実行できる手段があると便利
 → Jetpack Benchmarkが利用できる
 ボトルネックの解消


Slide 21

Slide 21 text

© ZOZO Technologies, Inc. 21 ● Instrumented testsと同様にBenchmarkのコードを書ける
 ● Android Studioからいつでも実行可能
 ● CIのフローに組み込みことも可能
 ○ 参考: Fighting regressions with Benchmarks in CI
 Jetpack Benchmark


Slide 22

Slide 22 text

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


Slide 23

Slide 23 text

© 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) } } }

Slide 24

Slide 24 text

© 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) } } }

Slide 25

Slide 25 text

© 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) } } }

Slide 26

Slide 26 text

© 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) } } }

Slide 27

Slide 27 text

© 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) } } }

Slide 28

Slide 28 text

© 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) } } }

Slide 29

Slide 29 text

© 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) } } }

Slide 30

Slide 30 text

© ZOZO Technologies, Inc. 30 商品CellのInflateパフォーマンスの改善
 
 ● 商品CellのInflateコストを削減
 ● スクロール時にジャンクを低減させる
 ● ベンチマーク結果を確認しながら改善
 


Slide 31

Slide 31 text

© ZOZO Technologies, Inc. 31 ● 方針
 ○ レイアウト構造のflatten化
 ○ レイアウトの遅延読み込み
 ● Android Developersの「レイアウトパフォーマンスを改善する」
 の内容が基本
 ○ https://developer.android.com/training/improving-layouts
 
 商品CellのInflateパフォーマンスの改善


Slide 32

Slide 32 text

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


Slide 33

Slide 33 text

© ZOZO Technologies, Inc. 33 ● 軽量なView
 ● ViewStub自身は何も描画しない
 ● layout属性で指定したレイアウトに任意のタイミングで置き換え
 
 ViewStub


Slide 34

Slide 34 text

© ZOZO Technologies, Inc. 34 ● 軽量なView
 ● ViewStub自身は何も描画しない
 ● layout属性で指定したレイアウトに任意のタイミングで置き換え
 
 ViewStub
 val view: View = findViewById(R.id.stub_import).inflate()

Slide 35

Slide 35 text

© 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(android.R.id.content) AsyncLayoutInflater(this) .inflate(R.layout.activity_main, content) { view, _, _ -> setContentView(view) } } }

Slide 36

Slide 36 text

© ZOZO Technologies, Inc. 36 ViewStub vs. AsyncLayoutInflater
 ● ViewStubは同期的、AsyncLayoutInflaterは非同期的にInflate
 ● 頻繁に使われるViewの置き換えにViewStubを使用
 ○ 同期的にInflateが行われるためジャンクが解消されない
 ○ 使用されないViewであればViewStubを使って問題ない
 ○ 例: onBindViewHolderで同期的にInflateが起きている
 
 
 


Slide 37

Slide 37 text

© ZOZO Technologies, Inc. 37 ● 商品Cellから次の特徴を持つViewを遅延読み込み
 ○ ネストが深い構造を持ったView
 ○ ホーム画面で使用されないView
 ■ 商品Cellは複数のリストで共有されている
 ○ Inflateのコストが高いView
 ■ Profilerの結果から確認
 
 AsyncLayoutInflaterでのViewの遅延読み込み


Slide 38

Slide 38 text

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


Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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


Slide 41

Slide 41 text

© ZOZO Technologies, Inc. 41 ● Profilerの結果では改善はされているが劇的に良くなった訳ではない
 ● RecyclerView自体のパフォーマンスチューニングも実施
 ○ チーム内での検証では体感でも改善された
 実際のスクロールパフォーマンスは改善されたのか?


Slide 42

Slide 42 text

© ZOZO Technologies, Inc. 42 ● Profilerの結果では改善はされているが劇的に良くなった訳ではない
 ● RecyclerView自体のパフォーマンスチューニングも実施
 ○ チーム内での検証では体感でも改善された
 ● 今後
 ○ 改善された状態で近日中にリリース(4/20時点で未リリース🙇)
 ○ 仕様検討まで含めて商品Cellの最適化を進める
 ○ Benchmarkの対象をInflateのではなくRecyclerViewとする
 実際のスクロールパフォーマンスは改善されたのか?


Slide 43

Slide 43 text

© ZOZO Technologies, Inc. まとめ
 43

Slide 44

Slide 44 text

© ZOZO Technologies, Inc. 44 ● Benchmarkベースで商品CellのInflateパフォーマンス改善をした
 ● だがスコアほど実際のボトルネックは解消されていない
 ● RecyclerViewのスクロールそのもののBenchmarkを取る等を行いよりパ フォーマンスを改善できる方法を探る
 まとめ


Slide 45

Slide 45 text

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


Slide 46

Slide 46 text

No content