跪拜 Guibai
← All articles
Frontend · Android · Flutter

R8 Eliminates Reflection Checks to Double Coroutine Launch Speed in Compose

By 恋猫de小郭 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Compose UI performance is sensitive to coroutine lifecycle overhead, not just to what the coroutine does. This build-time optimization removes a tax that every `LaunchedEffect`, `clickable`, and animation coroutine was paying, without requiring developers to change a single line of Kotlin code.

Summary

The performance bottleneck wasn't in the coroutine body but in the repeated reflection-based type checks that `AtomicReferenceFieldUpdater` performs on every atomic write. These checks appear constantly during coroutine initialization, state transitions, and parent-child task management in structured concurrency. Google's profiling showed that even an empty `LaunchedEffect` triggered a cascade of these calls, and in `Modifier.clickable`, roughly 80% of the time was spent just launching and cancelling internal coroutines.

R8, as a whole-program optimizer, can see at build time what the runtime cannot: that the holder type, field name, and value type are all effectively constant. It instruments the bytecode with a pre-computed field offset, replaces safe call sites with `Unsafe.compareAndSwapObject`, and then cleans up the original updater when all calls are optimized. The atomic operation itself stays the same; only the redundant safety checks are stripped away.

The result is a 2–4x speedup for atomic writes in `kotlinx.atomicfu` benchmarks, which translates to a 2x improvement in `LaunchedEffect` start/cancel micro-benchmarks. The gain is most visible in Compose's high-frequency UI paths, not in coroutines dominated by I/O or heavy computation. ART is also pursuing a runtime-level version of this optimization for devices on API 37 and above, which adds another ~15% improvement in JIT-compiled coroutine benchmarks.

Takeaways
R8 now rewrites `AtomicReferenceFieldUpdater.compareAndSet` into a direct `Unsafe.compareAndSwapObject` call when it can statically prove the holder type, field type, and value type.
Atomic write operations in `kotlinx.atomicfu` become 2–4x faster after the optimization, matching or exceeding plain `AtomicReference` performance.
An empty `LaunchedEffect` previously spent a large fraction of its time in `AtomicReferenceFieldUpdater` calls during init, state transitions, and completion.
About 80% of the cost of creating and updating `Modifier.clickable` was traced to launching and cancelling the internal `InteractionSource` coroutine.
The optimization is a three-phase R8 pass: instrumentation adds a pre-computed field offset, replacement swaps safe call sites, and clean-up removes dead updater fields.
ART is developing a runtime-level equivalent for devices on API 37+, which yields an additional ~15% improvement in JIT-compiled coroutine benchmarks.
The 2x speedup applies to coroutine lifecycle micro-benchmarks; coroutines whose cost is dominated by I/O or computation will not see a 2x overall speedup.
Conclusions

Compose's early workarounds—deferring coroutine creation, lazy initialization—were treating the symptom, not the cause. The real fix required a compiler-level understanding that the reflection checks were provably redundant at build time.

The optimization works because R8 operates on the whole program. A JIT compiler sees only hot paths and cannot safely delete the reflection logic without a global view of all call sites and field declarations.

`kotlinx.atomicfu` ends up faster than a hand-rolled `AtomicReference` in some cases because its compiler plugin inlines the field directly into the host object, eliminating both the wrapper allocation and the updater checks after R8's pass.

Concepts & terms
AtomicReferenceFieldUpdater
A Java utility that uses reflection to locate a volatile field by name and type, then performs atomic compare-and-swap operations on it. It avoids wrapping the field in a separate AtomicReference object but pays a runtime cost for type and access checks on every write.
R8 whole-program optimization
Android's default shrinker and optimizer that analyzes all classes in an app together. Unlike a JIT compiler, it can see every call site and class definition statically, enabling optimizations like removing reflection-based safety checks that are provably unnecessary.
Unsafe.compareAndSwapObject
A low-level JVM intrinsic that performs an atomic compare-and-swap on an object field given a direct memory offset. It bypasses all reflection and type-checking overhead, making it the fastest way to perform an atomic field update, but it requires the caller to guarantee correctness.
kotlinx.atomicfu
A Kotlin multiplatform library for atomic operations. On JVM/Android it delegates to AtomicReferenceFieldUpdater by default, but its compiler plugin can inline atomic properties directly as volatile fields in the host object, reducing object allocations.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗