Status Update
Comments
cl...@google.com <cl...@google.com>
ro...@gmail.com <ro...@gmail.com> #2
See attachment in this comment for the example project. The test project now uses dev17 version from yesterday, but the issues described in my first post are still reproducible.
Details for Android Studio version:
Android Studio 4.2 Canary 7
Build #AI-201.7846.76.42.6720134, built on July 30, 2020
Runtime version: 1.8.0_242-release-1644-b01 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
GC: ParNew, ConcurrentMarkSweep
Memory: 1237M
Cores: 8
Non-Bundled Plugins: org.jetbrains.kotlin
ro...@gmail.com <ro...@gmail.com> #3
ap...@google.com <ap...@google.com> #4
Branch: androidx-master-dev
commit 17cd20811cfd5df0dceb95706d76248f2f7f2e42
Author: Andrey Kulikov <andreykulikov@google.com>
Date: Tue Sep 22 20:09:35 2020
Scroll performance optimization for LazyColumnFor/LazyRowFor
We were unnecessary recomposing all the visible items on every scroll offset if the items type was not stable(if it was stable the recomposition was skipped using the regular runtime logic). In this change I introduced the itemContent lambda caching logic which allows to reuse the same lambda instance for not changed items. If we pass the same lambda instance into subcomposition the runtime will skip the whole composition as there is nothing to recompose. We clear this cache if the new itemContentFactory was provided or if the parent constraints have been changed.
Bug: 168293643
Bug: 167972292
Bug: 165028371
Relnote: Performance optimizations for LazyColumnFor/LazyRowFor scrolling by not doing unnecessary recompositions during every scroll
Test: tested manually and with a new test for both LazyColumnFor and LazyRowFor. Instead of 18 recompositions for each item for a simple scroll we do none of them.
Change-Id: I64f6568fd1193a6d28e3e2e2205b977f4a5f116b
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
A compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/CachingItemContentFactory.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyForState.kt
ro...@gmail.com <ro...@gmail.com> #5
ae...@google.com <ae...@google.com>
ap...@google.com <ap...@google.com> #6
Branch: androidx-master-dev
commit dc602125a9e5fc15a37ba4fa7610998a94ead29f
Author: Andrey Kulikov <andreykulikov@google.com>
Date: Fri Sep 25 21:14:09 2020
Do less work in SubcomposeLayout when subcompose called with the same lambda
If the lambda object for the slot didn't change and there were no pending recompositons we can skip the whole subcompositions invocation as it will result in unchanged tree. Starting subcomposition is a heavy weight operation so this change allows to improve the measure benchmarks for LazyColumn scrolling by 88 percents.
To achieve it I added a hasPendingChanges() method for Composition class.
Bug: 168293643
Bug: 167972292
Bug: 165028371
Relnote: The scrolling performance of LazyColumn/Row is improved by doing less work in subcomposition on every scroll. The new hasInvalidations() method was added for Composition class. hasPendingChanges() method from Recomposer was renamed to hasInvalidations()
Test: This is not changing the behavior so I can't cover it with a unit test, but it dramatically improves the benchmark. So if this optimization will stop working in the future we will get a regression.
Change-Id: Ib2f324dd6845fd83321e0d4f3fa6e502c346dbc3
M compose/runtime/runtime/api/current.txt
M compose/runtime/runtime/api/public_plus_experimental_current.txt
M compose/runtime/runtime/api/restricted_current.txt
M compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/AmbientTests.kt
M compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
M compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
M compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
M ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/ComposeIdlingResource.kt
M ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/CompositionAwaiter.kt
M ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
an...@google.com <an...@google.com> #7
One more way to optimize it is to add Modifier.drawLayer() directly on a LazyColumn item so it is cheaper to offset such items(feature request to do it automatically:
In your specific example you will need to add Modifier.drawLayer() for ConstraintLayout here
I will close the bug as fixed for now, Please feel free to comment again if after migrating to alpha05 and adding Modifier.drawLayer() for an item the scrolling performance is still unacceptable. Thanks!
ro...@gmail.com <ro...@gmail.com> #8
Hi, I just tested with Modifiler.drawLayer
added arround a wrappning Column
(tested with Row
/Column
version). This didn't make any difference, but as you mention in your previous post, the big optimization (88%, I read) is about to come in alpha05
, right? And I assume there isn't much difference in performance between using Row
/Column
and ConstraintLayout
(unlike nested LinearLayout
and ConstraintLayout
for views)? Correct me if I'm wrong.
Thanks a lot, looking forward to alpha05! ;)
ro...@gmail.com <ro...@gmail.com> #9
As for re-composition, yes, when I scroll I can see in Logcat
that I'm getting the message I added in the composable body several times (probably it's for each item that's coming into view).
And my Wallpost class is defined like this (you can also check the sample project I attached in the beginning of the discussion -
package com.example.wpcomposable
import com.example.wpcomposable.utils.DateTimeUtils
import java.util.*
import kotlin.collections.ArrayList
import kotlin.time.ExperimentalTime
data class Wallpost(
var id: String = "",
var content: String ="",
var datePosted: Date = Date(),
var username: String = "",
var comments: ArrayList<WallpostComment> = ArrayList(),
var liked: Boolean = false
) {
val headerText: String get() {
return "${this.username} on ${this.datePosted}"
}
val linksText: String get() {
return "Show previous comments (${this.comments.size} of 5) ---- Reply ----"
}
@ExperimentalTime
val timeEllapsed: String get() {
return DateTimeUtils.periodFromNowToString(this.datePosted)
}
}
an...@google.com <an...@google.com> #10
Columns and Row are currently a bit more performant than ConstraintLayout. So I would suggest to keep the version with Columns, add Modifier.drawLayer() on a first layout inside the item and wait for alpha05. Feel free to share you findings once you try it when this version will be released.
Thanks
ro...@gmail.com <ro...@gmail.com> #11
Thanks, for now it seems Modifier.drawLayer
doesn't improve things much - as you suggested I added at the top level of the composable body another wrapper Column
with this modifier. But I'm looking forward to alpha05
and will write back as soon as I've tested with it. :)
ro...@gmail.com <ro...@gmail.com> #12
I tried alpha06
, there's definitely improvement. But still scroll speed is far from that of a RecyclerView
. I can no longer consider it "freezing", but it's laggy when scrolling a little bit faster. I understand it's probably too early to measure the final version's performance, so I can comment again let's say after some last beta or RC version. ;) Thanks!
Description
That original XML view is ConstraintLayout so initially I migrated it to the equivalent ConstraintLayout composable. I also tried Row/Column approach, too. Both versions can be found in the links below. The performance issue is expressed in slow scrolling - the initial scroll seems to freeze for a few seconds before moving just to the second item (in total I have 128 items). So I think there's also a problem with scroll speed too, that is, the amount of views which are scrolled with single finger gesture.
Compared to the RecyclerView approach, even when I do fast scrolling everything is smooth and a single scroll goes through a lot more items in total (arround 20-30 items).
---
Setup information:
Jetpack compose 0.1.0-dev16
Phone: Nexus5X
Android 8.1 Oreo
Android Studio 4.2, latest Canary build
---
Following a discussion on #compose channel in Slack, I'm posting relevant code:
The item which is repeated, here are both ConstraintLayout and Row/Column versions:
ConstraintLayout -
Row/Column -
And the custom composables which are used (button and card image):
---
In the attachments you can find the images uesd: no_avatar.png is used in the RoundedImage composable, whereas user.xml is used with the standard Image composable as vector resource.