Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Strong Skipping Mode によってrecompositionはどう変わったのか
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
mikan
September 25, 2024
Technology
410
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Strong Skipping Mode によってrecompositionはどう変わったのか
DroidKaigi.onCompletion { 2024@Online }
https://yumemi.connpass.com/event/329691/
mikan
September 25, 2024
More Decks by mikan
See All by mikan
Lazy APIを使ってGradleビルド速度を改善する
mikanichinose
1
75
Navigation3でViewModelにデータを渡す方法
mikanichinose
0
700
「脳に収まるコードの書き方」を読んで学んだこと
mikanichinose
1
220
RepositoryのSSoT化
mikanichinose
0
94
Kotlin Multiplatform 始めました
mikanichinose
1
150
Web APIをなぜつくるのか
mikanichinose
0
3.9k
イベントをどう管理するか
mikanichinose
3
410
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
510
Modeling UiEvent
mikanichinose
0
140
Other Decks in Technology
See All in Technology
「ちゃんとやっている」は独りよがりだった ― 不安に寄り添うインシデント対応へ / Towards incident response that addresses anxieties
chmikata
1
520
“ID沼入口” - 基本とセキュリティから始める、考え続けるためのID管理技術勉強会 告知&イントロ
ritou
0
390
小さいから、全部わかる。— 常駐AI "xangi" のすすめ
sugupoko
0
260
背中から、背中へ /paying forward to community
naitosatoshi
0
220
認証認可だけじゃない! ID管理の構成要素と ライフサイクルを意識しよう
ritou
1
470
RAGの精度向上とエージェント活用
kintotechdev
2
130
FinOps X 2026 Recap from Engineer Side #JapanFinOps
chacco38
0
250
デジタル・デザイン:次の50年を描く「進化する青写真」
y150saya
0
810
トークン最適化のためのユーザーストーリー分析 / User Story Analysis for Token Optimization
oomatomo
0
170
AIペネトレーションテスト・ セキュリティ検証「AgenticSec」紹介資料
laysakura
2
8k
AIをフル活用してオンコール機能のプロトタイプを2日で作った話 / Building an AI-Powered On-Call Prototype in Just Two Days
nari_ex
0
180
Hatena Engineer Seminar 37 jj1uzh
jj1uzh
0
430
Featured
See All Featured
The agentic SEO stack - context over prompts
schlessera
0
840
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
240
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
460
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
200
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
2
290
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
Believing is Seeing
oripsolob
1
160
The Cult of Friendly URLs
andyhume
79
6.9k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
Google's AI Overviews - The New Search
badams
0
1.1k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Transcript
Strong Skipping Mode によって recompositionはどう変わったの か DroidKaigi.onCompletion { 2024@Online }
mikan(一瀬喜弘)
自己紹介
目的 Strong Skipping Mode を有効にすることで、不安定なパラメ ータに依存するComposable関数のrecompositionに、どのよ うな違いが生じるのか検証してみた 注意 コード例は検証用のものなので、およそプロダクションコー ドで利用するようなものにはなってません
Strong Skipping Mode とは TL;DR 不安定なパラメータに依存しているComposable関数も skippableがつくようになった
Strong Skipping Mode を有効にする 方法はいろいろある 1. compose-runtime:1.7.0 2. kotlin 2.0.20
3. compose compiler にオプションを渡す // build.gradle.kts // 1. tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() { compilerOptions.freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true", ) } composeCompiler { // 2. before kotlin 2.0.20 enableStrongSkippingMode = true // 3. after kotlin 2.0.20 featureFlags = setOf( ComposeFeatureFlag.StrongSkipping.disabled() // 無効にする書き方
検証用コード @Composable fun Names( names: List<String>, ) { Row( horizontalArrangement
= Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } }
検証用コード class UnstableUserClass( val names: List<String>, ) @Composable fun User(user:
UnstableUserClass) { Names(user.names) }
検証用コード data class UnstableUserDataClass( val names: List<String>, ) @Composable fun
User(user: UnstableUserDataClass) { Names(user.names) }
検証用コード @Stable class StableUserClass( val names: List<String>, ) @Composable fun
User(user: StableUserClass) { Names(user.names) }
検証用コード @Immutable class ImmutableUserClass( val names: List<String>, ) @Composable fun
User(user: ImmutableUserClass) { Names(user.names) }
検証用コード @Stable data class StableUserDataClass( val names: List<String>, ) @Composable
fun User(user: StableUserDataClass) { Names(user.names) }
検証用コード @Immutable data class ImmutableUserDataClass( val names: List<String>, ) @Composable
fun User(user: ImmutableUserDataClass) { Names(user.names) }
@Composable fun Users( unstableUserClass: UnstableUserClass, unstableUserDataClass: UnstableUserDataClass, stableUserClass: StableUserClass, immutableUserClass:
ImmutableUserClass, stableUserDataClass: StableUserDataClass, immutableUserDataClass: ImmutableUserDataClass, names: List<String>, modifier: Modifier = Modifier, ) { Column(modifier) { User(unstableUserClass) User(unstableUserDataClass) User(stableUserClass) User(immutableUserClass) User(stableUserDataClass) User(immutableUserDataClass) Names(names) } }
@Composable fun MainScreen1( count: Int, onChangeCount: (Int) -> Unit, modifier:
Modifier = Modifier, ) { val names = mutableListOf("mikan") val unstableUserClass = UnstableUserClass(names) val unstableUserDataClass = UnstableUserDataClass(names) val stableUserClass = StableUserClass(names) val immutableUserClass = ImmutableUserClass(names) val stableUserDataClass = StableUserDataClass(names) val immutableUserDataClass = ImmutableUserDataClass(names) Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
@Composable fun MainScreen2( count: Int, onChangeCount: (Int) -> Unit, modifier:
Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // positional memoized by remember val unstableUserClass = UnstableUserClass(names) val unstableUserDataClass = UnstableUserDataClass(names) val stableUserClass = StableUserClass(names) val immutableUserClass = ImmutableUserClass(names) val stableUserDataClass = StableUserDataClass(names) val immutableUserDataClass = ImmutableUserDataClass(names) Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
@Composable fun MainScreen3( count: Int, onChangeCount: (Int) -> Unit, modifier:
Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } val unstableUserClass = remember { UnstableUserClass(names) } val unstableUserDataClass = remember { UnstableUserDataClass(names) } val stableUserClass = remember { StableUserClass(names) } val immutableUserClass = remember { ImmutableUserClass(names) } val stableUserDataClass = remember { StableUserDataClass(names) } val immutableUserDataClass = remember { ImmutableUserDataClass(names) } Column(modifier) { Text("Count: $count") Button({ names += "mikan" onChangeCount(count + 1) }) { Text("Increment") } Users( unstableUserClass, unstableUserDataClass, stableUserClass, immutableUserClass, stableUserDataClass, immutableUserDataClass, names, ) } }
MainScreen1 すべてrecompositionした。recompositionの前後で見た目は変わらず → recompositionの度にすべての変数が再割り当てされているから → StableUserDataClassとImmutableUserDataClassがrecompositionした理由について、この説明だと違和感 が残る
MainScreen2 (mutableListにrememberをつけたやつ) @Stable と @Immutable を つけた data class だけスキップした。
recompositionした ものは 描画が 更新された
MainScreen2 (mutableListにrememberをつけたやつ) @Composable fun MainScreen2( count: Int, onChangeCount: (Int) ->
Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュが存在するので再割り当ては発生しない val unstableUserClass = UnstableUserClass(names) // UnstableUserClass(["mikan", "mikan"]) → UnstableUserClass(["mikan", "mikan"]) val unstableUserDataClass = UnstableUserDataClass(names) // UnstableUserDataClass(["mikan", "mikan"]) → UnstableUserDataClass(["mikan", "mikan"]) val stableUserClass = StableUserClass(names) // StableUserClass(["mikan", "mikan"]) → StableUserClass(["mikan", "mikan"]) val immutableUserClass = ImmutableUserClass(names) // ImmutableUserClass(["mikan", "mikan"]) → ImmutableUserClass(["mikan", "mikan"]) val stableUserDataClass = StableUserDataClass(names) // StableUserDataClass(["mikan", "mikan"]) → StableUserDataClass(["mikan", "mikan"])
MainScreen2 (mutableListにrememberをつけたやつ) @Composable fun MainScreen2( count: Int, onChangeCount: (Int) ->
Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュが存在するので再割り当ては発生しない val unstableUserClass = UnstableUserClass(names) // skippableでないのでrecompositionする val unstableUserDataClass = UnstableUserDataClass(names) // skippableでないのでrecompositionする val stableUserClass = StableUserClass(names) // skippableだが、再割り当てによって参照が変わっているのでrecompositionする val immutableUserClass = ImmutableUserClass(names) // skippableだが、再割り当てによって参照が変わっているのでrecompositionする val stableUserDataClass = StableUserDataClass(names) // skippableであり、equals比較において同じとみなされるのでrecompositionしない
MainScreen3 (変数すべてにrememberをつけたやつ) @Stable と @Immutable をつけたものについてはスキップした。recompositionしたものは描画が更新された
MainScreen3 (変数すべてにrememberをつけたやつ) @Composable fun MainScreen3( count: Int, onChangeCount: (Int) ->
Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserClass = remember { UnstableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserDataClass = remember { UnstableUserDataClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val stableUserClass = remember { StableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val immutableUserClass = remember { ImmutableUserClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない val stableUserDataClass = remember { StableUserDataClass(names) } // recomposition時: キャッシュされているため再割り当ては発生しない
MainScreen3 (変数すべてにrememberをつけたやつ) @Composable fun MainScreen3( count: Int, onChangeCount: (Int) ->
Unit, modifier: Modifier = Modifier, ) { val names = remember { mutableListOf("mikan") } // recomposition時: キャッシュされているため再割り当ては発生しない val unstableUserClass = remember { UnstableUserClass(names) } // skippableでないのでrecompositionする val unstableUserDataClass = remember { UnstableUserDataClass(names) } // skippableでないのでrecompositionする val stableUserClass = remember { StableUserClass(names) } // skippableであり、再割り当ては発生していないのでスキップ val immutableUserClass = remember { ImmutableUserClass(names) } // skippableであり、再割り当ては発生していないのでスキップ val stableUserDataClass = remember { StableUserDataClass(names) } // skippableであり、再割り当ては発生していないのでスキップ
Strong Skipping Mode MainScreen1 すべてrecompositionした: 変化なし
Strong Skipping Mode MainScreen2 (mutableListにrememberをつけたやつ) @Stable と @Immutable をつけた data
class および listを単純に渡しているものについてはスキップした → listはキャッシュが使われるのでスキップしたと考えられる
Strong Skipping Mode MainScreen3 (変数すべてにrememberをつけたやつ) すべてスキップした → すべてskippableになり、再割り当ては発生していないのでスキップしたものと考えられる
Strong Skipping Mode 気になった点 @Composable fun Names( names: List<String>, )
{ Row( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { names.forEach { Text(it) } } } Names(listOf("mikan")) // skip
まとめ Strong Skipping Mode を有効化することで、不安定な型であっても同値であればrecompositionをスキップ するようになった 公式ドキュメントには、不安定な型については === で比較するとあったが、 ==
で比較しているように 見える これまでたまたま描画が更新できていた箇所が、Strong Skipping Mode を有効化によって更新しなくなる 可能性がある
ご清聴ありがとうございました