Jetpack Compose Performance – System Trace, Recomposer, and the Truth About Frames
Jetpack Compose did not eliminate performance problems.
It changed their shape.
Apps no longer suffer from deep view hierarchies,
but they now suffer from:
- uncontrolled recomposition,
- expensive state propagation,
- missed frame deadlines hidden behind “low CPU usage”.
This article explains how Compose performance truly behaves at runtime, using System Trace as the source of truth.
Frames Are the Only Currency Users Care About
Users do not experience:
- recompositions,
- allocations,
- CPU percentages.
They experience frames.
Every frame has:
- a deadline (~16.6 ms at 60 Hz),
- a strict schedule,
- zero tolerance for excuses.
Miss the deadline, and users feel it immediately.
The Rendering Pipeline (Compose Edition)
Before profiling Compose, you must understand the pipeline:
- VSYNC arrives
- Choreographer schedules a frame
- Compose recomposes (if needed)
-
applyChangesupdates the UI tree - RenderThread submits GPU commands
Profiler data only makes sense inside this sequence.
System Trace: The Only Place Where Frames Exist
CPU Profiler tells you what ran.
System Trace tells you when frames were missed.
📸 Figure 8 – System Trace: Compose Frame Lifecycle
(Insert image here)
Android System Trace showing the full Compose frame lifecycle from VSYNC through recomposition and rendering.
Annotations (number these on the image):
- VSYNC – frame start signal
- Choreographer#doFrame – scheduling phase
- Recomposer.runRecomposeAndApplyChanges
- applyChanges duration
- RenderThread execution
Critical insight:
If applyChanges misses the deadline, no optimization elsewhere matters.
Recomposer: Friend, Not Foe
Recomposition is cheap by design.
What hurts performance is what you do during recomposition.
📸 Figure 9 – Recomposer Activity in System Trace
(Insert image here)
System Trace highlighting Recomposer activity and applyChanges duration impacting frame rendering.
Annotations:
- Recomposer scheduling
- Long applyChanges blocks
- Back-to-back recompositions
Senior insight:
Frequent recomposition is fine.
Expensive recomposition is fatal.
SnapshotStateObserver: The Messenger You Blame
When Compose state is read, SnapshotStateObserver tracks it.
When performance is bad, it shows up everywhere.
That does not make it the villain.
📸 Figure 10 – SnapshotStateObserver Hotspots
(Insert image here)
CPU flame chart showing SnapshotStateObserver overhead caused by inefficient state reads in Jetpack Compose.
Annotations:
- SnapshotStateObserver dominating CPU time
-
recordReadOfin hot paths - State read at the wrong composable scope
Root cause:
One state change invalidates too much UI.
Skipped Frames: When Users Feel Jank Before Logs Do
Profiler only flags severe jank.
Users feel:
- uneven pacing,
- subtle stutters,
- animation inconsistency.
These appear as VSYNC gaps.
📸 Figure 11 – Skipped Frames via VSYNC Gaps
(Insert image here)
System Trace revealing skipped frames through large gaps between VSYNC signals during animations.
Annotations:
- Large VSYNC gaps
- Frame duration exceeding deadline
- Idle RenderThread waiting for Main thread
Reality check:
Smoothness is statistical, not binary.
Why “Low CPU Usage” Means Nothing in Compose
Compose work often:
- runs in short bursts,
- blocks at terrible moments,
- hides behind averages.
A 4 ms stall at the wrong time breaks a frame.
CPU charts will not scream.
System Trace will.
Pre-Release Compose Performance Checklist
Before shipping a Compose-heavy screen:
- Recomposition is scoped and intentional
-
applyChangesconsistently finishes within frame budget - No large VSYNC gaps during scroll or animation
- SnapshotStateObserver does not dominate hot paths
If any of these fail, users will notice.
What This Part Ties Together
- Part 1 explained threads and deadlines
- Part 2 explained memory pressure
- This part shows how everything collapses into frames
Frames are where all performance debts are paid.
Final Thought
Junior developers ask:
“Why is Compose recomposing so much?”
Senior developers ask:
“Which frame missed its deadline, and why?”
System Trace already knows.
You just need to listen.
Series Navigation
- Part 1 – CPU & Thread Reality
- Part 2 – Memory, GC, and Leaks
- Part 3 – Compose, System Trace, and Frames ← you are here
- Part 4 – Common Profiler Misreads
Top comments (0)