As a follow up, I finally got some decent tracing ...
# compose-ios
a
As a follow up, I finally got some decent tracing out of my iOS app, and I'm seeing very little work done on the
_dispatch_workloop_worker_thread
(s); does that mean virtually all the app's functionality is working on the main thread?
In looking through the tasks that are running on not-worker-threads (but I don't what they are), is a lot of
kotlinx.atomicfu
locks:
It would seem I am doing something very very wrong
a
That's really depends on what your're doing in your project and how you're using threads to unload the main thread. Currently, Compose Multiplatform on iOS (as any UI framework) is mostly running on main thread, and it includes both, recomposition and rendering. If it looks like the application have some unexpected lags and frame drops on iOS/Desktop/Web, and does not have on Android, please feel free to create a minimal reproducer and file an issue in our YouTrack. Now it's just a guessing, however,
SynchronizedObject
is used inside Compose Snapshot system - and it can cause performance drops if you're updating your app state too frequently.
a
Thanks for the reply @Andrei Salavei ! This app is updated in real-time with a websocket, so there are is a fair amount of backed-end pushes that need to result in UI updates. I am seeing quite a bit of performance degradation on iOS along with a problematic thermal state - the phone gets hot! I'm going to keep digging, but a couple of questions: • My Jetpack Compose code is making calls that at least in theory hop across dispatchers - from dispatchers.main, to dispatchers.io, and back to the UI. Should this operate as it does in Android on iOS? Or behind the scenes is it just running everything on main thread? • What is the proper way to offload network operations to the IO/worker threads in KMP iOS? I'm using a
URLSessionWebSocketTask,
and I guess I don't know which thread it's on. But it doesn't appear to be on the worker threads on iOS. • What's the way to minimize
SynchronizedObject
utilizations? Just use compose best practices/minimize recomps? Thanks for any assistance; this app has a lot of functionality and could be a great KMP/CMP case study, so I really wanna get it right!
1
a
@Andrew Watson, could you please share your performance measurements snapshot from Instruments - I would like to check it, if possible. Also, I cannot answer all the questions as I'm not an expert in coroutines.
Should this operate as it does in Android on iOS? Or behind the scenes is it just running everything on main thread?
Coroutines on iOS and Android work pretty much the same. So there is no implicit dispatches in the main queue.
What is the proper way to offload network operations to the IO/worker threads in KMP iOS? I'm using a
URLSessionWebSocketTask,
and I guess I don't know which thread it's on. But it doesn't appear to be on the worker threads on iOS.
The
URLSessionWebSocketTask
dispatches result callback on background (e.g. working) queue. FYI you can check
NSURLSession
documentation, as well as iOS dispatching model (which calls GCD).
What's the way to minimize
SynchronizedObject
utilizations? Just use compose best practices/minimize recomps?
Sure, best practices will help you here.
Ah, and all performance measurements should be done with release configuration - it significantly changes the situation.
n
What's the way to minimize
SynchronizedObject
utilizations?
What Compose version do you use? We don't use
atomicfu SynchronizedObject
implementation in Compose Runtime starting from Compose 1.7.0
a
@Nikita Lipsky it turns out we are using Compose 1.6.7, which I was just a bit surprised by. I thought I had updated! Thanks for all the information here everyone; I have a lot I can use to move forward and figure out what is going on
Okay @Nikita Lipsky, some interesting updates. For a little back story, what started the whole ordeal of me tracing my app and the like was actually getting a severe hang sometimes when running the app. I was unable to reproduce the first time around but saw the interesting performance/`SynchronizedObject` details in the logs. Well now I've been able to reproduce both things - the hang/`SynchronizedObject` in tracing, and they might be the same issue. I'm including a modified screenshot of my trace logs, and as you can see I've highlighted the severe hang for 8 seconds:
You'll also notice that its the
kotlinx.atomicfu.SynchronizedObject
running for most of those 8 seconds. The child trace of that object looks like a garbage collection operation. The kicker here is that I've upgraded compose to 1.7.5, plugin to 1.7.0. So a couple questions: • Is this likely a memory leak/running out of memory-based hang? • Why is
SynchronizedObject
still being used in Compose 1.7 code? Note: I am using kotlin compiler 1.9.23 Any thoughts you have on this would be super helpful!
Final point: this is atomicfu code running on the main thread
a
Hey @Andrew Watson, Please check your actual compose version in project dependencies.. We don't have CMP 1.7.5. The latest version we released is 1.7.1. Also, for deep analysis it's not enough to see screenshot. Could you please export measurements data from instruments?
n
> Is this likely a memory leak/running out of memory-based hang? It seems to be a very long GC pause. However to confirm this, please send more frames under
SynchronizedObject.lock
. Now it is nor clear what actually eats those 8s. > Why is
SynchronizedObject
still being used in Compose 1.7 code? Could you show the stack trace above
SynchronizedObject.lock
, who actually calls it? > Note: I am using kotlin compiler 1.9.23 Please try Kotlin 2.0.21: there are many GC performance improvements in it. If the problem still persists with it, please create a reproducer and file an issue in Kotlin issue tracker.
a
Actually, I can do better, here is the trace log file to be opened in instruments. When you open it you'll see the hang; it's the big red hang at the last half of the timeline. You can select the main thread and any highlight any duration to walk the stack trace
In summary, looking up the tree hierarchy, it appears to be a series of Flows, StateFlows & SharedFlows emitting & collecting, but for what purpose I can't quite discern. What I can tell you is that the hang is reproduced when scrolling through a long, paginated list at a decent speed. I'll try the compiler upgrades and see how things go!
v
Could you please also tell what coroutines version are you using?
n
@Andrew Watson Thank you for the trace! > Why is
SynchronizedObject
still being used in Compose 1.7 code? From the trace, we see that the usages of
SynchronizedObject
are from iOS coroutines implementation not from Compose Runtime. As you see above, we have created an issue on this but if you could provide a reproducer as well it would facilitate investigations.
a
It looks like we are using coroutines-core 1.8.0. screenshot included with other deps as well
Want me to go comment on ticket as well?
And for a reproducer, you'd want source code/a project right?
n
> Want me to go comment on ticket as well? Yes, please, thank you. > And for a reproducer, you'd want source code/a project right? Yes. Ideally a project on the github with a reproducing scenario. It can be any code that reproduces the scenario (e.g. a stripped down version of the scenario in you app)
a
Update! I had been checking on a different, older branch of my app. The branch that actually generated the freeze and trace file/image I posted Tues & Wed are actually this set of dependencies, which includes
kotlinx-coroutines-core
1.9.0
🙏 1
I've also just updated the GitHub ticket
Hey @Nikita Lipsky some updates: • Kotlin 2.1.0. Unfortunately looking at system trace output, it looks almost identical to the system tracing I got before upgrading everything. I haven't been able to freeze the app yet, but with the tracing looking so similar, I think it might be possible • I'm still seeing the same amounts of
kotlinx.atomicfu.locsk.SychronizedObject
utilizations as before, but not in the UI as you noted; it's lower level operatios • One thing I was wondering; my app is making heaving use of
SharedFlow
; our app is based on websockets, and the library I inherited utilizes shared flows to emit update events and the like. I notice in the tracing what calls
SynchronizedObject
ends up being
SharedFlowImpl.tryTakeValue
; should I attempt to rework my code to minimize SharedFlow usage?
I have updated the GitHub issue, but can't attach the trace file, so will provide here: