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은 다음 네 가지 변환을 수행합니다:

  1. 필드의 메모리 오프셋 식별. R8은 객체 레이아웃 내에서 대상 필드의 정확한 위치를 계산합니다.
  2. updater 객체 제거. 리플렉티브 래퍼(reflective wrapper)가 사라져 메모리가 절약되고 클래스 로딩 작업이 제거됩니다.
  3. 직접적인 sun.misc.Unsafe 호출 삽입. 이 저수준 API는 단일 atomic 하드웨어 명령어를 사용하여 필드에 값을 씁니다.
  4. 모든 updater 호출을 새로운 unsafe 명령어로 교체. 이를 통해 JIT 컴파일러가 해당 작업을 인라이닝할 수 있게 합니다.

결과적으로 CPU는 더 이상 리플렉티브 룩업이나 런타임 체크를 수행할 필요 없이, atomic 명령어를 직접 실행하게 됩니다. 개발자 관점에서는 코루틴 API의 동작이 동일하므로 변화를 느낄 수 없지만, 내부적으로 코드는 "하드웨어 수준"의 속도로 실행됩니다.

측정 가능한 이점

일반적인 Android 기기에서 AGP 9.2.0을 사용하고, R8을 활성화하며, 코드 축소(minification)를 켠 상태로 빌드했을 때 다음과 같은 속도 향상이 나타났습니다:

  • Channel send/receive: 2.01배 빠름
  • Mutex lock/unlock: 1.90배 빠름
  • StateFlow updates: 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에 대한 직접적인 호출이 보일 것입니다.

주의 사항 및 반론

이 최적화는 모든 프로젝트가 충족하지는 않는 두 가지 조건에 달려 있습니다:

  1. Minification이 활성화되어 있어야 합니다. 디버그 빌드나 디버깅 편의를 위해 minification을 꺼둔 릴리스 빌드에서는 이점을 얻을 수 없습니다.
  2. ProGuard 규칙이 허용적이어야 합니다. 코루틴 내부 구현에 대해 공격적인 -keep 패턴을 사용하는 프로젝트는 해당 규칙을 완화해야 할 수도 있으며, 이 경우 주의 깊게 테스트하지 않으면 내부 클래스가 코드 축소(shrinking) 관련 버그에 노출될 수 있습니다.

타겟팅하는 다양한 기기에서 테스트하는 것이 여전히 권장되는 관행입니다.

다음에 주목할 점

이번 리라이트는 빌드 타임 바이트코드 변환을 통해 언어 추상화 뒤에 숨겨진 성능을 어떻게 끌어낼 수 있는지 보여줍니다. 특정 워크로드에서 실제 성능 향상을 확인하려면 코루틴을 많이 사용하는 본인의 코드를 프로파일링해 보시기 바랍니다.

핵심 요약: AGP 9.2.0으로 업그레이드하고 R8의 minification을 활성화하면, 빌드 설정이 최적화 도구가 제 역할을 수행할 수 있도록 허용한다는 전제하에, 소스 코드를 전혀 수정하지 않고도 코루틴을 많이 사용하는 Kotlin 앱의 핵심 동기화 속도를 거의 두 배로 높일 수 있습니다.