agentsclimarketplace

Kmp performance

Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/performance/kmp-performance

Performance tuning for Kotlin Multiplatform — coroutines on iOS, the new memory model, cold-start cost of the shared framework, and avoiding common Kotlin/Native footguns. Use when profiling a KMP app that feels slow on iOS.From its SKILL.md

Install
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill kmp-performance

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.7 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it

KMP Performance

Instructions

Most KMP performance problems surface on iOS because Kotlin/Native compiles AOT to a large framework binary. Fix them in this order: cold start → memory model misuse → dispatcher misuse → allocation hot paths.

1. Cold start

The iOS framework binary is loaded on app launch. Large exported API surfaces and heavy top-level initializers pay at startup.

  • Shrink exported symbols. Only export composition roots. Everything else internal.
  • Avoid work in top-level val/object initializers. Kotlin/Native runs them eagerly on first access; with a large dependency graph they cascade. Use by lazy (thread-safe by default).
  • Do not initialize Koin / HTTP / DB at framework load. Wire those on first screen.
  • Measure with Instruments → App Launch, and Xcode's DYLD_PRINT_STATISTICS=1.

2. The new memory model (default since Kotlin 1.9)

The old "freezing" model is gone. Shared mutable state across threads is legal but should still be minimized:

  • Prefer StateFlow/SharedFlow for cross-thread communication.
  • Use kotlinx.atomicfu for atomic counters; it compiles to real atomics on all targets.
  • Do not add @SharedImmutable/freeze() — they're deprecated and do nothing useful.

If you see "InvalidMutabilityException" in logs, you are on the legacy MM. Force the new one:

# gradle.properties
kotlin.native.binary.memoryModel=experimental   # already default; harmless belt-and-braces

3. Dispatchers on iOS

  • Dispatchers.Main maps to the UIKit main queue — use it only for UI updates.
  • Dispatchers.Main.immediate — dispatches synchronously if already on main. Prefer it for state-flow collectors feeding SwiftUI.
  • Dispatchers.IO exists on Kotlin/Native (coroutines 1.7+) and uses a bounded thread pool. Use for file / socket / blocking work. Do not reach for newSingleThreadContext { } except for strictly-serial state.
  • Dispatchers.Default uses the standard pool for CPU work.

Common mistake: doing all work on Dispatchers.Main because "Swift seems fine" — the main thread serializes with UIKit drawing and causes jank. Always withContext(Dispatchers.IO) { repo.load() }.

4. Hot-path allocations

Kotlin/Native's GC is generational but pauses more than Android ART on large heaps. Reduce allocations in tight loops:

  • Prefer buildList { } over repeated +.
  • For numeric arrays, use IntArray/FloatArray instead of List<Int>.
  • Avoid String.format on hot paths — it allocates heavily on iOS.
  • Cache Regex, Json, and DateTimeFormatter instances at top-level by lazy.

5. Flow performance

  • flowOn(Dispatchers.Default) upstream expensive maps — otherwise they run on the collector.
  • stateIn(SharingStarted.WhileSubscribed(5_000)) deduplicates subscriptions across screens.
  • distinctUntilChanged() on flows whose upstream emits unchanged values.
  • Do not .collect { } inside a LaunchedEffect(Unit) that keys on Unit if the flow depends on a changing parameter — you'll leak subscriptions.

6. Serialization & networking

  • Json { ignoreUnknownKeys = true; explicitNulls = false } at a single top-level instance.
  • Ktor's ContentNegotiation reuses the Json — don't re-create per call.
  • For very large responses, consider Json.decodeFromStream on JVM or Json.decodeFromBufferedSource with kotlinx-io. Avoid reading the full body into String first.

7. SQLDelight

  • Put queries on Dispatchers.IO: .asFlow().mapToList(Dispatchers.IO).
  • Use transaction { } for multi-write paths — a single transaction is orders of magnitude faster on iOS WAL.
  • Index columns you WHERE on; EXPLAIN QUERY PLAN works via the CLI.

8. Profiling

  • Android: Android Studio CPU Profiler + Perfetto.
  • iOS: Xcode Instruments — Time Profiler for CPU, Allocations for GC churn, Network for Ktor traffic.
  • Shared benchmarks: kotlinx-benchmark for commonMain microbenchmarks that run on JVM + Native.

Checklist

  • Exported API trimmed; top-level initializers are by lazy.
  • No frozen/sharedImmutable legacy annotations remain.
  • Repository/IO work dispatched on Dispatchers.IO, never Main.
  • Json/Regex/formatters are cached, not re-instantiated per call.
  • Flows use flowOn upstream and stateIn(WhileSubscribed) where appropriate.
  • SQLDelight queries run on Dispatchers.IO and multi-write paths use transaction.
  • Cold start and a key screen profiled on a real iOS device.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,871. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.