[Android][Kotlin] BaselineProfile

D.O·2023년 10월 10일
1

이번 글에서는 이전에 알아보았던 Benchmark와 BaselineProfile을 이용해서 App 성능 향상 및 최적화를 하는 방법에 대해 알아보겠습니다.

BaseLine Profile

Baseline Profile은 Android 7(API 수준 24) 이상에서 사용할 수 있는 프로필을 제공하는 새로운 메커니즘입니다.

BaseLineProfile에 대해 알아보기전에 이전 글에서 언급하였던 JIT와 AOT에 대해 알아보고 가겠습니다.

Benchmark 글에서 언급하였듯이 CompliationMode에는 4가지가 있는데요

각 방식은 AOT를 얼마만큼 사용할지를 결정한다고 생각하시면 됩니다.

AOT(Ahead-of-Time) 컴파일과 JIT(Just-in-Time) 컴파일은 프로그램의 실행 방식을 결정하는 두 가지 주요 컴파일 전략입니다.

AOT(Ahead-of-Time) 컴파일

AOT 컴파일은 프로그램 실행 전에 코드를 기계어로 변환하는 방식입니다.

장점

  • 실행 속도: 컴파일된 코드가 직접 실행되기 때문에 런타임 시의 추가적인 변환 없이 빠르게 실행됩니다.
  • 적은 오버헤드: 실행 전에 코드가 이미 컴파일되어 있으므로 런타임 중에 발생할 수 있는 JIT 관련 오버헤드가 없습니다.

단점

  1. 컴파일 시간: 초기 컴파일에 시간이 걸릴 수 있습니다. 즉, 앱을 설치하는데 오랜 시간이 걸린다는 것입니다.
  2. 크기: 컴파일된 코드는 일반적으로 인터프리터나 JIT 코드보다 크기가 큽니다

JIT (Just-in-Time) 컴파일

JIT 컴파일은 프로그램이 실행될 때 코드를 기계어로 변환하는 방식입니다.

장점

  1. 적응성: 실행 중에 실제로 사용되는 코드만 컴파일하므로, 실행 패턴에 따라 최적화할 수 있습니다.
  2. 메모리 사용: 실행 중에 필요한 코드만 메모리에 로드하므로 메모리 사용이 효율적일 수 있습니다.

단점

  1. 초기 오버헤드: 처음 코드가 실행될 때 컴파일이 필요하므로, 초기 실행 속도가 느릴 수 있습니다.
  2. 최적화 한계: AOT 방식에 비해 최적화 수준이 제한적일 수 있습니다

JIT,AOT 혼합 도입

이전 버전의 Android에서는 애플리케이션 설치 시 AOT 컴파일을 사용하여 코드를 기계 코드로 사전 컴파일했습니다. 그러나 이 접근 방식은 설치 시간이 길어지고, 기기의 저장 공간을 많이 차지하는 문제가 있었습니다.

이러한 문제를 극복하고자 Android N부터 Android는 AOT (Ahead-Of-Time) 컴파일JIT (Just-In-Time) 컴파일혼합한 방식을 도입했습니다. 애플리케이션 설치 시에는 기본적인 사전 컴파일만 수행하고, 사용자가 앱을 실행할 때 JIT 컴파일러를 사용하여 런타임에 필요한 부분만을 컴파일하는 방식을 도입했습니다. 이로 인해 앱의 설치 속도가 향상되고 저장 공간이 절약되었습니다.

여러 기술의 도입

이 JIT와 AOT 방식 혼합이 이루어진 후에 많은 최적화 기술이 도입이되었습니다.

또한, 앱 사용 중에는 프로파일링 정보를 수집하여, 이 정보를 기반으로 주기적으로 배경에서 AOT 컴파일을 수행하는 “Profile-Guided Optimization (PGO)” 방식도 도입되었습니다.

또한 Google Play는 Android 9(API 수준 28)부터 Cloud Profile도 제공합니다. 앱이 기기에서 실행될 때 ART에서 생성된 프로필은 Play Store 앱에 의해 업로드되고 클라우드에서 집계됩니다. 애플리케이션에 대해 업로드된 프로필이 충분히 많아지면, Play 앱은 후속 설치에 집계된 프로필을 사용합니다.

문제

하지만 이 방법 모두 초기 단계에서의 앱의 성능을 최적화하지는 못한다는 단점이 있습니다.

초기 단계에 앱의 성능은 매우 중요합니다.

또한 많은 앱이 매주 업데이트되기 때문에 이러한 방법은 새로운 방법의 도입을 고민하게 하였습니다.

해결책 BaseLineProfile

해결책으로 등장한 것은 BaseLineProfile이였습니다.

Baseline Profile은 Android 7(API 수준 24) 이상에서 사용할 수 있는 프로필을 제공하는 새로운 메커니즘입니다

"BaseLine Profile"은 Android 런타임(ART)이 설치 중에 중요한 경로를 기계어 코드로 사전 컴파일하는 데 사용하는 APK에 포함된 클래스 및 메서드 목록입니다. "BaseLine Profile의 주요 목적은 개발자에게 앱의 주요 실행 경로를 미리 지정할 수 있는 방법을 제공하여 앱의 성능을 개선하는 것입니다.

즉, 설치가 진행될 때 ART가 프로필에서 메서드의 AOT(Ahead-of-time) 컴파일을 실행하여 메서드의 실행 속도를 높입니다. 프로필이 앱 실행 시 또는 프레임 렌더링 중에 앱에서 사용하는 메서드를 포함하는 경우, 사용자가 경험하는 실행 시간이 빨라지고 버벅거림이 줄어듭니다.

공식문서에서는 BaseLine Profile는 앱 또는 라이브러리를 개발할 때, 시작, 전환, 스크롤과 같이 렌더링 시간이나 지연 시간이 중요성을 갖는 중요한 사용자 여정의 특정 주요 경로를 포괄하는 비즈니스 프로필을 정의하여 사용할 수 있습니다. 라고 설명하고 있습니다.

이렇게 정의한 기준 프로필은 APK와 함께 Google Play를 통해 사용자에게 직접 제공됩니다 따라서 사용자는 앱을 처음 실행 할때부터 BaselineProfile에 의해 앱의 주요 함수 및 클래스가 사전 컴파일된 앱을 사용할 수 있습니다.

BaseLineProfile 생성하기

이제 이러한 BaseLineProfile을 생성하는 방법에 대해서 설명하겠습니다

저는 BaselineProfileGenerator를 구현하여 baselineprofile 을 생성했습니다.

이 구현은 앱을 시작하고 홈 화면 로딩 및 스토리 화면 로딩 및 스크롤에 대한 베이스라인 프로파일 시뮬레이션만을 포함하고 있습니다. 이러한 특정 범위의 시뮬레이션을 선택한 주된 이유는 홈 화면과 스토리 화면이 사용자의 주요 상호작용 포인트, 앱의 전반적인 사용자 경험에 결정적인 영향을 미치므로, 이를 기준으로 프로파일링하는 것은 효율적이라 판단했습니다

저는 실제 하드웨어 기기에서 진행하였습니다.
에뮬레이터에서 하려면 별도의 작업이 필요합니다
에뮬레이터로 진행하시는분은 아래 codelab을 참고하세요

https://developer.android.com/codelabs/android-baseline-profiles-improve?hl=ko#5

해당 Generator 코드가 성공적으로 실행이 완료되면

아래처럼 해당 파일이 속해있는 모듈의build/outputs/connected_android_test_Additional_output에 BaselineProfileGenerator의 결과물이 생깁니다.

클릭해보시면 알아보기 힘든 내용이 적혀있습니다.
사전 컴파일된 함수 및 클래스의 목록입니다.
이를 이용해 앱에서 사용할 수 있습니다.

이제 이것을app 모듈의 main소스세트에 복사하면됩니다.
경로 : app/src/main/baseline-prof.txt

BaselineProfile 평가

baseline-prof를 활용하여 사전 컴파일의 효과를 평가하기 위해 이전에 작성하였던 벤치마크 테스트를 다시 실행 해봅시다.

여기서 이전에 배웠던 CompliationMode가 유용합니다.

baseline-prof를 사용하는 방식과 사용하지 않는 방식으로 테스트를 나누어 진행하였습니다. 이를 통해 baseline-prof에 의한 사전 컴파일이 얼마나 효과적으로 작동하는지를 평가할 수 있습니다.

  1. startUpNoCompilation()는 아무런 컴파일 최적화 없이 앱의 시작 성능을 측정합니다. 즉, 아무런 사전 컴파일 없이 앱이 얼마나 빠르게 시작되는지를 테스트합니다.
  2. startupBaselineProfile()은 Baseline Profile을 사용하여 사전 컴파일을 수행한 후 앱의 시작 성능을 측정합니다. Baseline Profile은 앱의 실행 패턴을 기반으로 컴파일을 최적화하여 앱의 시작 시간을 줄이는 방법 중 하나입니다.

아래는 baseline-profile을 이용하여 startup_benchmark를 한 결과입니다.

이 BenchMark글에서 언급했듯이 공식문서에 따르면 여러 노이즈로 인한 측정의 오류를 최소화하기 위해서 여러 Iteration 중 가장 성능이 잘 나온 경우인 min 값에 관심을 가지라고 나와있습니다.

따라서 min값에 대해서 성능을 분석하면 BaselineProfile을 사용했을 때와 사용하지 않았을 때의 최소 실행 시간 차이는 20.2ms입니다.

개인적으로 평균값이 아니기 때문에 높은 Iteration에서는 중간값도 의미가 있다고 생각합니다. 중간값에 대한 실행 시간 차이는 22.6ms입니다.

향상도 (%)=(변화 전 값−변화 후 값) / 변화 전 값 ×100 이라고 했을 때 BaselineProfile을 사용함으로써 앱의 시작 시간이 약 7.5% 정도 향상된 것을 볼 수 있습니다

복잡한 앱 일수록 BaselineProfile로 인한 성능 차이가 더 커질 것이라고 생각이 듭니다.

아래는 제가 만든 앱의 주요 로직인 Story를 스크롤하는 로직에 대한 Benchmark를 한 결과 입니다.

공식문서에 따르면 각각의 p50 p90 p95, p99 등은 이러한 측정값은 50번째, 90번째, 95번째, 99번째 백분위수로 분포를 나타낸다고 합니다.

  • P50 (50th percentile, 중앙값): 모든 측정값의 중앙에 위치하는 값입니다. 이는 전체 벤치마크 테스트에서 평균적인 성능을 나타냅니다.
  • P90 (90th percentile): 모든 측정값 중 상위 10%에 위치하는 값입니다. 이는 대부분의 경우에 해당 벤치마크가 어떻게 수행되는지 나타냅니다.
  • P95 (95th percentile): 모든 측정값 중 상위 5%에 위치하는 값입니다. 이는 거의 모든 경우에서 해당 벤치마크가 어떻게 수행되는지 나타냅니다.
  • P99 (99th percentile): 모든 측정값 중 상위 1%에 위치하는 값입니다. 이는 최악의 성능 시나리오에서 해당 벤치마크가 어떻게 수행되는지 나타냅니다.

이러한 결과의 어디에 관심을 가져야 하는가에 대한 가이드라인이 적혀있지 않아서 제 생각을 말하자면 백분위수는 특정 Iteration에 대한 측정값을 나타내는 것이 아니라 전체적인 분포를 반영하므로, 노이즈에 의한 측정 오류의 영향이 적다고합니다. 따라서 모든 값에 대해 목적을 가지고 관심을 가져야 한다고 생각을 합니다.

이제 각각의 성능 향상도를 계산해보겠습니다.

1. frameDurationCpuMs (CPU에서의 프레임 처리 시간):

  • P50:
    • BaseLineProfile 사용: 8.0ms
    • BaseLineProfile 미사용: 8.0ms
    • 향상도: (8.0−8.0)/8.0×100 = 0% (8.0−8.0)/8.0×100
  • P90:
    • BaseLineProfile 사용: 15.0ms
    • BaseLineProfile 미사용: 14.6ms
    • 향상도: (14.6−15.0)/15.0×100 = -2.67% (14.6−15.0)/15.0×100
  • P95:
    • BaseLineProfile 사용: 24.5ms
    • BaseLineProfile 미사용: 24.5ms
    • 향상도: (24.5−24.5)/24.5×100 = 0% (24.5−24.5)/24.5×100
  • P99:
    • BaseLineProfile 사용: 32.7ms
    • BaseLineProfile 미사용: 50.0ms
    • 향상도: (50.0−32.7)/50.0×100 = 34.6% (50.0−32.7)/50.0×100

2. frameOverrunMs (프레임 오버런 시간):

  • P50:
    • BaseLineProfile 사용: 2.8ms
    • BaseLineProfile 미사용: 3.6ms
    • 향상도: (3.6−2.8)/3.6×100 = 22.22% (3.6−2.8)/3.6×100
  • P90:
    • BaseLineProfile 사용: 8.4ms
    • BaseLineProfile 미사용: 11.3ms
    • 향상도: (11.3−8.4)/11.3×100 = 25.66% (11.3−8.4)/11.3×100
  • P95:
    • BaseLineProfile 사용: 21.1ms
    • BaseLineProfile 미사용: 21.7ms
    • 향상도: (21.7−21.1)/21.7×100 = 2.76% (21.7−21.1)/21.7×100
  • P99:
    • BaseLineProfile 사용: 36.9ms
    • BaseLineProfile 미사용: 50.9ms
    • 향상도: (50.9−36.9)/50.9×100 = 27.51% (50.9−36.9)/50.9×100

아래는 개인적인 분석 견해가 포함되어있습니다.

frameDurationCpuMs에서는 p99를 제외하고 그다지 큰 변화가 없습니다. P99에 대해서만 갑작스러운 변화가 있기에 이것은 노이즈에 대한 일시적인 향상이라고 생각했습니다. 따라서 frameDurationCpuMs에서는 큰 변화가 없다는게 제가 생각한 분석 결과입니다

더 자세한 결과를 얻기 위해서는 여러번의 테스트가 필요하다고 생각이 듭니다.

frameOverrunMs 경우에는 전반적으로 높은 향상을 보여줍니다. 이는 모든 부분에서 향상이 있기 때문에 BaseLineProfile로 인한 컴파일이 성능 향상이 확실히 일어났다고 생각합니다.

마무리

"BaseLineProfile에 대한 역사, 사용법, 그리고 분석 방법(개인적인 견해를 포함하여)을 살펴보았습니다.

개인적으로, 단지 Benchmark를 작성하고 주요 부분에 대한 시나리오만 구성하는 것이 이처럼 놀라운 성능 향상을 가져올 수 있다는 사실에 놀랐습니다.

결과적으로, 시작 시간에서는 약 7.5%, 앱의 주요 비즈니스 로직에서는 frameOverrunMs 지표가 약 25% 향상되었습니다.

다른 주요 비즈니스 로직에서도 한번 테스트해봐야겠습니다.

BaseLineProfile 간단하고 무조건 좋아보이기만하지만 사용 할 때 주의 및 고려할 점도 있습니다.

  1. "BASE-PROFILE"의 효과는 프로파일링된 코드 경로와 실제 사용자의 사용 패턴에 따라 다를 수 있습니다. 따라서, 실제 성능 향상을 검증하기 위해서는 실제 기기에서의 테스트사용자 피드백을 반영하는 것이 중요합니다.
  2. Android와 같은 모바일 플랫폼에서는 저장 공간과 성능 사이의 균형을 찾아야 합니다. 따라서 AOT 컴파일을 적용할 때는 이러한 트레이드오프를 고려하여 최적의 전략을 선택해야 합니다. 따라서, 중요한 코드 경로나 빈번히 사용되는 코드만 AOT 컴파일을 적용하는 것이 하나의 방법이라고 생각이 됩니다.

또한 이러한 시스템 최적화는 앱 자체의 코드 품질이나 알고리즘의 효율성을 개선하는 것이 아닙니다. 따라서 앱의 성능 최적화와 관련하여 개발자가 직접 수행해야 할 작업 (예: 효율적인 알고리즘 )선택, 불필요한 리소스 사용 줄이기 등)도 여전히 많습니다.

다음 이러한 앱 자체의 코드 품질이나 알고리즘의 효율성을 개선하는 방법에 대한 글을 가져오겠습니다.!

긴 글 읽어주셔서 감사합니다.

profile
Android Developer

0개의 댓글