DEV Community

ViO Tech
ViO Tech

Posted on

Jetpack Compose Performance – System Trace, Recomposer, and the Truth About Frames

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:

  1. VSYNC arrives
  2. Choreographer schedules a frame
  3. Compose recomposes (if needed)
  4. applyChanges updates the UI tree
  5. 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)

PLACEHOLDER – Compose Frame Lifecycle

Android System Trace showing the full Compose frame lifecycle from VSYNC through recomposition and rendering.

Annotations (number these on the image):

  1. VSYNC – frame start signal
  2. Choreographer#doFrame – scheduling phase
  3. Recomposer.runRecomposeAndApplyChanges
  4. applyChanges duration
  5. 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)

PLACEHOLDER – Recomposer Activity

System Trace highlighting Recomposer activity and applyChanges duration impacting frame rendering.

Annotations:

  1. Recomposer scheduling
  2. Long applyChanges blocks
  3. 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)

PLACEHOLDER – SnapshotStateObserver Flame Chart

CPU flame chart showing SnapshotStateObserver overhead caused by inefficient state reads in Jetpack Compose.

Annotations:

  1. SnapshotStateObserver dominating CPU time
  2. recordReadOf in hot paths
  3. 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)

PLACEHOLDER – VSYNC Gaps

System Trace revealing skipped frames through large gaps between VSYNC signals during animations.

Annotations:

  1. Large VSYNC gaps
  2. Frame duration exceeding deadline
  3. 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
  • applyChanges consistently 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)