Android 빌드 도구가 Kotlin Coroutines의 성능을 대폭 향상시켰습니다. Android Gradle Plugin (AGP) 9.2.0과 내장된 R8 shrinker를 통해, 코루틴 상태 처리를 담당하는 바이트코드가 재작성되어 가장 일반적인 코루틴 작업에서 약 2배의 성능 향상을 제공합니다. 코루틴을 사용하는 Android 앱을 배포하는 개발자는 Kotlin 코드를 단 한 줄도 수정하지 않고도 더 빠른 콜드 스타트 시간, 더 안정적인 프레임 레이트, 그리고 소폭 개선된 배터리 수명을 경험할 수 있습니다.
코루틴 성능이 중요했던 이유
Kotlin Coroutines는 여러 스레드에서 내부 필드를 안전하게 수정하기 위해 AtomicFieldUpdater 객체를 사용합니다. 이 방식은 업데이트마다 새로운 atomic 객체를 할당하는 것은 피하지만, Android에서는 세 가지 숨겨진 비용을 발생시킵니다:
- 클래스 로딩 지연 – updater가 리플렉션(reflection)을 통해 생성되므로, VM은 런타임에 대상 필드를 찾아야 합니다.
- CPU 낭비 – 각 접근 시 실제 필드에 도달하기 전에 updater의 상태를 확인해야 합니다.
- 인라이닝 제한 – 컴파일러가 updater 호출을 인라이닝할 수 없어, 생성된 바이트코드의 크기가 커지고 속도가 느려집니다.
실제로 이러한 비용은 채널 송신/수신(channel send/receive), 뮤텍스 잠금/해제(mutex lock/unlock), StateFlow 업데이트, 코루틴 디스패치(coroutine dispatch)와 같은 핫 패스(hot paths)에서 나타납니다. 그 결과, 앱 본연의 작업보다는 관리 작업(bookkeeping)에 상당한 CPU 시간이 소모되는 결과가 발생합니다.
AGP 9.2.0 및 R8에서 변경된 점
AGP 9.2.0에 포함된 코드 축소 도구인 R8은 이제 컴파일된 바이트코드를 스캔하여 표준 AtomicFieldUpdater 패턴을 찾아냅니다. 패턴을 발견하면 R8은 다음 네 가지 변환을 수행합니다:
- 필드의 메모리 오프셋 식별. R8은 객체 레이아웃 내에서 대상 필드의 정확한 위치를 계산합니다.
- updater 객체 제거. 리플렉티브 래퍼(reflective wrapper)가 사라져 메모리가 절약되고 클래스 로딩 작업이 제거됩니다.
- 직접적인
sun.misc.Unsafe호출 삽입. 이 저수준 API는 단일 atomic 하드웨어 명령어를 사용하여 필드에 값을 씁니다. - 모든 updater 호출을 새로운 unsafe 명령어로 교체. 이를 통해 JIT 컴파일러가 해당 작업을 인라이닝할 수 있게 합니다.
결과적으로 CPU는 더 이상 리플렉티브 룩업이나 런타임 체크를 수행할 필요 없이, atomic 명령어를 직접 실행하게 됩니다. 개발자 관점에서는 코루틴 API의 동작이 동일하므로 변화를 느낄 수 없지만, 내부적으로 코드는 "하드웨어 수준"의 속도로 실행됩니다.
측정 가능한 이점
일반적인 Android 기기에서 AGP 9.2.0을 사용하고, R8을 활성화하며, 코드 축소(minification)를 켠 상태로 빌드했을 때 다음과 같은 속도 향상이 나타났습니다:
- Channel send/receive: 2.01배 빠름
- Mutex lock/unlock: 1.90배 빠름
StateFlowupdates: 2.02배 빠름- Coroutine dispatch: 1.68배 빠름
이 수치들은 실질적인 사용자 경험 개선으로 이어집니다. 코루틴 동기화를 기다리느라 0.몇 초를 소비하던 콜드 런칭(cold launch)이 더 빨리 완료되어, UI 스레드가 첫 프레임을 렌더링할 수 있는 여유를 더 많이 갖게 됩니다. 또한 CPU 경합이 줄어들면 프로세서가 더 빨리 절전 모드로 돌아갈 수 있어 배터리 수명 개선에도 도움이 됩니다.
이점을 누리는 방법
코드 변경은 필요하지 않습니다. 이 재작성 기능을 활성화하려면 다음 조건이 필요합니다:
- AGP 9.2.0 이상 – 업데이트된 R8이 포함된 버전입니다.
- R8 – 위 AGP로 빌드할 때 자동으로 사용됩니다.
- Kotlin Coroutines 1.8.0 이상 – 최적화 도구가 기대하는
AtomicFieldUpdater패턴이 포함된 라이브러리 버전입니다. - 릴리스 빌드 타입에
isMinifyEnabled = true설정 – R8은 코드 축소가 활성화되었을 때만 실행됩니다.
유일하게 추가로 해야 할 작업은 ProGuard(또는 R8) 규칙을 검토하는 것입니다. volatile 필드나 updater 클래스 자체를 보존하는 광범위한 -keep 지시문은 재작성을 방해합니다. 규칙이 R8을 통해 해당 필드를 수정할 수 있도록 허용하는지 확인하십시오. 그렇지 않으면 최적화 도구는 원래의 리플렉티브 구현 방식으로 되돌아갑니다(fallback).
Android Studio의 APK Analyzer를 통해 변환 여부를 확인할 수 있습니다. 컴파일된 APK를 열고 JobSupport와 같은 코루틴 지원 클래스를 찾아 디컴파일된 바이트코드를 검사하십시오. 재작성에 성공했다면 정적 updater 필드가 사라지고 대신 Unsafe에 대한 직접적인 호출이 보일 것입니다.
주의 사항 및 반론
이 최적화는 모든 프로젝트가 충족하지는 않는 두 가지 조건에 달려 있습니다:
- Minification이 활성화되어 있어야 합니다. 디버그 빌드나 디버깅 편의를 위해 minification을 꺼둔 릴리스 빌드에서는 이점을 얻을 수 없습니다.
- ProGuard 규칙이 허용적이어야 합니다. 코루틴 내부 구현에 대해 공격적인
-keep패턴을 사용하는 프로젝트는 해당 규칙을 완화해야 할 수도 있으며, 이 경우 주의 깊게 테스트하지 않으면 내부 클래스가 코드 축소(shrinking) 관련 버그에 노출될 수 있습니다.
타겟팅하는 다양한 기기에서 테스트하는 것이 여전히 권장되는 관행입니다.
다음에 주목할 점
이번 리라이트는 빌드 타임 바이트코드 변환을 통해 언어 추상화 뒤에 숨겨진 성능을 어떻게 끌어낼 수 있는지 보여줍니다. 특정 워크로드에서 실제 성능 향상을 확인하려면 코루틴을 많이 사용하는 본인의 코드를 프로파일링해 보시기 바랍니다.
핵심 요약: AGP 9.2.0으로 업그레이드하고 R8의 minification을 활성화하면, 빌드 설정이 최적화 도구가 제 역할을 수행할 수 있도록 허용한다는 전제하에, 소스 코드를 전혀 수정하지 않고도 코루틴을 많이 사용하는 Kotlin 앱의 핵심 동기화 속도를 거의 두 배로 높일 수 있습니다.
