Hey team, I’m running a UI performance benchmark a...
# compose
a
Hey team, I’m running a UI performance benchmark across CMP / Android / iOS / Flutter using a lazy list of rotating (heavy) local images. iOS (SwiftUI) and Flutter perform very well. CMP/Android shows bad performance when using
Image
from Compose directly, why is that? • Using a loading library like Coil or Sketch ◦ I got really good results on Android native ◦ On CMP I got bad results on iOS (not surprising) BUT I got pretty bad results also on Android (drop frame)... I assumed CMP code on Android would behave similarly to native since it’s Compose under the hood. Could this be due to the image loading libraries (Coil/Sketch) behaving differently in CMP? Any idea on how improving these results on CMP/android?
To be more accurate, I got pretty good results using Sketch on iOS, but quite poor on Android. And with Coil, it is the contrary, I even got crashes on iOS (out of memory). But still, on cmp/android it is far from android native results.
s
on cmp/android it is far from android native results
What is the native implementation you are comparing it to?
I wonder if that's due to mpp resource system being slower smh
m
Why should that matter? I hope a resource is only loaded once and not each time it is rotated.
s
Idk, hard to say without profiles really
👌 1
t
Most likely due to improper usage. You have 300 items and the list is not stable in compose unless you mark it as such. No caching strategy for the images, no keys for the lazy view etc
k
first of all you haven't even used
key(..)
so you are enforcing lazy diffing on off-screen rows while you enforce
9000
matrix operations all at once in the craziest way possible, what are you doing - this isn't benchmark, you're butchering compose not even using any APIs in the way they should be, then rushing to blame compose for it 👌 you are spinning up a
rememberInfiniteTransition
recreating it 300 times while you
.rotate(angle)
on 30 children inside each row, triggering 9000 matrix updates and rerasterizations every 16 ms without even a key() to setup diff on list items
Copy code
items(300) in the outer LazyColumn × items(30) in every inner LazyRow
 → 300 rows × 30 cells = 9 000 composables that hold a graphicsLayer rotation each frame.
you're also enforcing decoding of each image on compose while in your flutter it aggressively caches by default, you should have used
painterResource
etc.. with key parameters. edit: finally though, these are not the worst, worst offenders. The worst thing in your code was
val infiniteTransition = rememberInfiniteTransition()
executing 300 compositional scopes for each row, which should have been a single global transition at the top level if people used compose properly they would notice that it's better in every way
☝🏻 1
☝️ 2
a
Thanks for all the answers. Actually, I did implemented some of the mentioned optimizations from the start (like using painterResource, and putting rememberInfiniteTransition as a global transition, have a key), but they didn’t yield any noticeable improvement. But still, in my opinion, the issue doesn’t lie there: 👉 I have the exact same code in a native Android repo, and I don’t experience the same performance problems. If it were just an optimization issue, the results should be similar.
h
Also make sure you are comparing release builds on Android, not debug builds if you are doing that.
👍 1
a
Yes that’s a good point, and yes it is release for both. I’ll keep digging and I’ll make also a commit with all perf improvements.
j
@Anthony Mamode I think you should edit your original message until you’ve done a fair comparison (perf optimizations & release build). You’re results are conclusive but readers of this channel may come across your message and assume the CMP is not ready for production.
a
@Jonathan Thanks for the follow-up. Actually I did push the extra perf commit, but again the initial problem was not really having bad performances but having a significative difference between CMP (on android) compare to Android native, even if the compose code was exactly the same. Also, as I said in past messages, suggested perf improvement do not improve the results. • Using native Image component is not working well because image are really heavy, Coil is working pretty well though. • Putting a key does not change anything as there is not replace/update/delete, etc. on the lists. • Hoisting the animation, I thought will have impact, but don’t. Of course I may still miss something. so if anyone is able to run the list on 60 fps on low-end devices, I’d really appreciate the contribution, as I’m stuck for now.
t
Did you use stable type for your list?
👌 1
s
I suggest to capture a method trace with profiler in Android Studio and check what your app is doing this way
👍 1
p
What if instead of having repeatedPictureList as List<String>, prepare Painter for each drawable and make repeatedPictureList as List<Painter> and adopt your example for it?
a
@PHondogo Yes I do try this the first time! Using Image instead of coil and preparing painters to avoid recreating them. But Image cannot properly handle scale down of big images as Coil does. So perf are pretty bad.
e
Which Jetpack Compose version did you compare it to?
a
Im using Compose Multiplatform 1.8.1 for CMP and was using Compose BOM 2024.09.00 for Android and just try the latest 2025.06.00 with same results
👍 1