Skip to content

v1.0 — SubcomposeLayout vs Direct Canvas: A Compose List Rendering Study

Latest

Choose a tag to compare

@Aofled Aofled released this 24 Sep 17:35
· 1 commit to main since this release

A controlled Macrobenchmark comparison of five ways to load images in a Jetpack
Compose list, focused on one question: how much does SubcomposeLayout actually
cost, compared to plain Canvas drawing or plain recomposition?

Full write-up, methodology, and results table: docs/benchmark/2026-coil-subcomposition/SUMMARY.md

Strategies compared

  1. AsyncImage (Direct Canvas) — zero subcomposition
  2. AsyncImage + Composable Overlay — Canvas image, Composable error screen only on failure
  3. SubcomposeAsyncImage (named slots: loading / success / error)
  4. SubcomposeAsyncImage (monolithic content lambda, manual when(state))
  5. rememberAsyncImagePainter + manual when(state) in a plain Box

Setup

  • 500 cards, fully local assets, dynamic aspect ratios, deterministic per-item decode
    delay and error injection — no network involved anywhere.
  • Two physical devices: Samsung SM-T595 (Android 10, API 29) and Samsung SM-G9750
    (Android 12, API 31). No emulator.
  • CompilationMode.Full(), StartupMode.WARM, release-like build (R8 minified,
    non-debuggable, profileable), fixed theme, system animations off, airplane mode on.

Key findings

  • The bottleneck depends on the device class, not just the API. On the older
    device, the SubcomposeAsyncImage strategies fall behind on P90/P95. On the
    flagship, the ranking flips — rememberAsyncImagePainter becomes the worst
    performer instead, due to a documented API quirk unrelated to subcomposition.
  • The effect lives in P90/P95, not in P99. The tail (P99) is dominated by a factor
    shared by all five strategies; the architectural difference shows up clearly one
    step before that.
  • AsyncImage (Direct Canvas) is fastest overall, but has no real error screen.
    Among the strategies that do render a proper Composable error UI, AsyncImage +
    Composable Overlay is the most consistent choice, at almost no measured cost over
    the fastest option.

What's attached to this release

  • Aggregated result tables
  • The results chart (frame_duration_by_percentile.png)

Reproducibility

  • Code snapshot: this tag
  • Kotlin 2.4.20 · Compose BOM 2026.09.00 · Coil 3.6.2 · AGP 9.4.0 · Hilt 2.60.1
  • Dataset: 500 items, fixed decode delays and error flags (seeded, not randomized)