우수사례: 앱 시작 시간을 50% 개선한 Gmail Wear OS팀

앱 시작은 사용자에 대한 앱의 첫인상을 나타냅니다. 사용자는 기다리는 것을 좋아하지 않으므로 앱이 빠르게 시작되도록 해야 합니다. 실제 앱 개발팀이 앱 시작과 관련된 문제를 어떻게 발견하고 진단했는지 알아보기 위해 Gmail Wear OS팀이 수행한 작업은 다음과 같습니다.

Gmail Wear OS팀에서는 팀의 앱 성능 기준을 충족하기 위해 앱 시작 및 런타임 렌더링 성능에 특히 중점을 둔 최적화 작업을 시작했습니다. 그러나 타겟팅할 특정 기준점이 없더라도 시간을 들여 앱 시작을 개선하면 거의 항상 앱 시작을 개선할 여지가 있습니다.

트레이스 캡처 및 앱 시작 확인

분석을 시작하려면 Perfetto 또는 Android 스튜디오에서 자세히 검사하기 위해 앱 시작이 포함된 트레이스를 캡처합니다. 이 우수사례에서는 Perfetto를 사용하는데, 그 이유는 앱 외에도 기기 시스템 전반에서 발생하는 상황을 보여주기 때문입니다. Perfetto에서 트레이스를 업로드하면 다음과 같이 표시됩니다.

그림 1. Perfetto에서의 트레이스 초기 뷰

앱 시작을 개선하는 데 중점을 두므로 Android App Startups 맞춤 측정항목으로 행을 찾습니다. 행 위로 마우스를 가져가면 표시되는 고정 아이콘 을 클릭하여 뷰 상단에 고정하는 것이 좋습니다. Android App Startups 행에 표시되는 막대 또는 슬라이스는 첫 번째 앱 프레임이 화면에 그려질 때까지 앱 시작이 적용되는 시간 범위를 나타내므로 문제나 병목 현상이 있는지 확인해야 합니다.

고정 옵션이 강조 표시된 Android App Startups 행
그림 2. 보다 쉽게 분석할 수 있도록 Android App Startups 커스텀 측정항목을 대시보드 상단에 고정하세요.

Android App Startups 측정항목은 reportFullyDrawn()를 사용하더라도 처음 표시하는 데 걸린 시간을 나타냅니다. 완전히 표시하는 데 걸린 시간을 식별하려면 Perfetto 검색창에서 reportFullyDrawn()를 검색합니다.

기본 스레드 확인

먼저 기본 스레드에서 어떤 일이 일어나고 있는지 확인합니다. 기본 스레드는 일반적으로 모든 UI 렌더링이 발생하는 위치이므로 매우 중요합니다. 기본 스레드가 차단되면 그리기가 발생할 수 없으며 앱이 정지된 것처럼 보입니다. 따라서 기본 스레드에서 장기 실행 작업이 발생하지 않도록 해야 합니다.

기본 스레드를 찾으려면 앱의 패키지 이름이 있는 행을 찾아 펼칩니다. 패키지와 이름이 같은 두 행 (일반적으로 섹션의 처음 두 행)은 기본 스레드를 나타냅니다. 두 기본 스레드 행 중 첫 번째 행은 CPU 상태를 나타내고 두 번째 행은 tracepoint를 나타냅니다. Android App Startups 측정항목 아래에 두 개의 기본 스레드 행을 고정합니다.

Android App Startups 및 기본 스레드 행이 고정되었습니다.
그림 3. 분석을 돕기 위해 Android App Startups 맞춤 측정항목 아래에 기본 스레드 행을 고정합니다.

실행 가능한 상태 및 CPU 경합에 소요된 시간

앱 시작 중에 CPU 활동의 집계 뷰를 가져오려면 기본 스레드 위로 커서를 드래그하여 앱 시작 시간 범위를 캡처합니다. 표시되는 Thread States 패널은 선택한 시간 범위 내에 각 CPU 상태에서 소요된 총 시간을 보여줍니다.

Runnable 상태에서 소요된 시간을 확인합니다. 스레드가 Runnable 상태에 있는 경우 스레드는 작업을 실행할 수 있지만 예약된 작업은 없습니다. 이는 기기의 로드가 과부하되어 우선순위가 높은 작업을 예약할 수 없음을 나타낼 수 있습니다. 사용자에게 표시되는 최상위 앱의 예약 우선순위가 가장 높으므로 유휴 기본 스레드는 앱 내의 집약적인 프로세스(예: 애니메이션 렌더링)가 CPU 시간을 두고 기본 스레드와 경쟁함을 나타내는 경우가 많습니다.

대화목록 상태 패널에서 여러 상태의 총 시간이 강조 표시된 기본 대화목록
그림 4. Runnable~Running 상태의 상대적 시간을 평가하여 CPU 경합이 어느 정도인지 초기에 파악합니다.

Runnable 상태의 시간과 Running 상태의 시간 비율이 높을수록 CPU 경합이 발생할 가능성이 높습니다. 이러한 방식으로 성능 문제를 검사할 때는 가장 오래 실행되는 프레임에 먼저 집중하고 더 작은 프레임에 집중합니다.

Runnable 상태에서 소요된 시간을 분석할 때는 기기 하드웨어를 고려하세요. 설명된 앱은 CPU가 두 개 있는 웨어러블 기기에서 실행되므로 CPU가 더 많은 기기에서 볼 때보다 Runnable 상태에서 더 많은 시간을 보내고 다른 프로세스와 더 많은 CPU 경합이 예상됩니다. 일반적인 전화 앱에서는 예상보다 Runnable 상태에 더 많은 시간이 소요되지만 웨어러블 기기의 맥락에서는 이를 이해할 수 있습니다.

OpenDexFilesFromOat* 사용 시간

이제 OpenDexFilesFromOat*에 소요된 시간을 확인합니다. 트레이스에서는 bindApplication 슬라이스와 동시에 발생합니다. 이 슬라이스는 애플리케이션의 DEX 파일을 읽는 데 걸린 시간을 나타냅니다.

차단된 바인더 트랜잭션

다음으로 바인더 트랜잭션을 확인합니다. 바인더 트랜잭션은 클라이언트와 서버 간의 호출을 나타냅니다. 이 경우 앱 (클라이언트)은 binder transaction로 Android 시스템(서버)을 호출하고 서버는 binder reply로 응답합니다. 앱이 시작 중에 불필요한 바인더 트랜잭션을 수행하지 않도록 합니다. CPU 경합이 발생할 위험이 증가하기 때문입니다. 가능하면 앱 시작 기간 후에 바인더 호출이 포함된 작업을 연기하세요. 바인더 트랜잭션을 실행해야 하는 경우 기기의 Vsync 새로고침 빈도보다 오래 걸리지 않도록 해야 합니다.

일반적으로 ActivityThreadMain 슬라이스와 동시에 발생하는 첫 번째 바인더 트랜잭션은 매우 긴 것으로 보입니다. 발생할 수 있는 상황에 대해 자세히 알아보려면 다음 단계를 따르세요.

  1. 연결된 바인더 응답을 확인하고 바인더 트랜잭션의 우선순위 지정 방식을 자세히 알아보려면 원하는 바인더 트랜잭션 슬라이스를 클릭합니다.
  2. 바인더 응답을 보려면 현재 선택 패널로 이동하여 팔로우 중인 대화목록 섹션에서 바인더 답장을 클릭합니다. Thread 필드는 또한 개발자가 수동으로 이동하려고 할 때 바인더 응답이 발생하는 스레드를 알려줍니다. 이 스레드는 다른 프로세스에 있게 됩니다. 바인더 트랜잭션과 응답을 연결하는 줄이 표시됩니다.

    선이 바인더 호출과 응답을 연결합니다.
    그림 5. 앱 시작 중에 발생하는 바인더 트랜잭션을 식별하고 지연할 수 있는지 평가합니다.
  3. 시스템 서버에서 이 바인더 트랜잭션을 처리하는 방식을 확인하려면 Cpu 0Cpu 1 스레드를 화면 상단에 고정합니다.

  4. 바인더 회신 스레드 이름(이 경우 'Binder:687_11 [2542]')이 포함된 슬라이스를 찾아 바인더 응답을 처리하는 시스템 프로세스를 찾습니다. 관련 시스템 프로세스를 클릭하여 바인더 트랜잭션에 관한 자세한 정보를 확인합니다.

CPU 0에서 발생하는 관심 있는 바인더 트랜잭션과 관련된 다음 시스템 프로세스를 살펴보세요.

종료 상태가 '실행 가능 (선점됨)'인 시스템 프로세스
그림 6. 시스템 프로세스가 Runnable (Preempted) 상태이며 지연되고 있음을 나타냅니다.

종료 상태에는 Runnable (Preempted)가 표시됩니다. 즉, CPU가 다른 작업을 하고 있기 때문에 프로세스가 지연되고 있습니다. 무엇으로 선점되는지 알아보려면 Ftrace 이벤트 행을 펼칩니다. 사용할 수 있는 Ftrace 이벤트 탭에서 스크롤하여 관심 있는 바인더 스레드 'Binder:687_11 [2542]'와 관련된 이벤트를 찾습니다. 시스템 프로세스가 선점되는 무렵에 'decon' 인수가 포함된 시스템 서버 이벤트가 두 개 발생했습니다. 즉, 디스플레이 컨트롤러와 관련이 있습니다. 디스플레이 컨트롤러가 프레임을 화면에 표시하기 때문에 이는 합리적으로 보입니다. 이는 중요한 작업입니다. 그런 다음 이벤트를 그대로 둡니다.

관심 있는 바인더 트랜잭션과 관련된 FTrace 이벤트가 강조표시됨
그림 7. FTrace 이벤트는 바인더 트랜잭션이 디스플레이 컨트롤러 이벤트에 의해 지연되고 있음을 나타냅니다.

JIT 활동

JIT(just-in-time) 컴파일 활동을 조사하려면 앱에 속한 프로세스를 확장하고 2개의 'Jit 스레드 풀' 행을 찾아 뷰 상단에 고정합니다. 이 앱은 앱 시작 중에 기준 프로필을 활용하므로 첫 번째 Choreographer.doFrame 호출의 끝으로 표시되는 첫 번째 프레임이 그려질 때까지 JIT 활동이 거의 발생하지 않습니다. 그러나 느린 시작 원인 JIT compiling void를 확인합니다. 이는 Application creation 라벨이 지정된 tracepoint 중에 발생하는 시스템 활동이 많은 백그라운드 JIT 활동을 유발함을 나타냅니다. 이 문제를 해결하려면 프로필 모음을 앱을 사용할 준비가 된 지점까지 확장하여 첫 번째 프레임을 그린 직후에 발생하는 이벤트를 기준 프로필에 추가합니다. 대부분의 경우 특정 UI 위젯이 화면에 표시될 때까지 기다리는 라인을 추가하여 화면이 완전히 채워졌음을 나타내는 기준 프로필 컬렉션 Macrobenchmark 테스트 끝에 행을 추가하면 됩니다.

'Jit 컴파일 무효' 슬라이스가 강조 표시된 Jit 스레드 풀
그림 8. JIT 활동이 많이 표시되면 앱을 사용할 준비가 된 지점까지 기준 프로필을 확장합니다.

결과

이 분석의 결과로 Gmail Wear OS팀은 다음과 같이 개선되었습니다.

  • CPU 활동을 볼 때 앱 시작 중에 경합이 발생했으므로 앱이 단일 정적 이미지로 로드 중임을 나타내는 데 사용되는 스피너 애니메이션을 대체했습니다. 또한 스플래시 화면을 연장하여 쉬머 상태(앱이 로드 중임을 나타내는 데 사용되는 두 번째 화면 상태)를 지연시켜 CPU 리소스를 확보했습니다. 그 결과 앱 시작 지연 시간이 50% 개선되었습니다.
  • OpenDexFilesFromOat*JIT 활동에서 소요된 시간을 확인하여 기준 프로필R8 재작성을 사용 설정했습니다. 그 결과 앱 시작 지연 시간이 20% 개선되었습니다.

다음은 앱 성능을 효율적으로 분석하는 방법에 관한 개발팀의 팁입니다.

  • trace와 결과를 자동으로 수집할 수 있는 진행 중인 프로세스를 설정합니다. 벤치마킹을 사용하여 앱에 자동화된 추적을 설정해 보세요.
  • 개선될 것으로 생각되는 변경사항에는 A/B 테스트를 사용하고, 개선되지 않으면 거부합니다. Macrobenchmark 라이브러리를 사용하여 다양한 시나리오에서 성능을 측정할 수 있습니다.

자세한 내용은 다음 리소스를 참조하세요.