Jetpack Benchmark는 Android App의 성능을 측정하는 Jetpack Library 입니다.
일반적으로 하기 쉬운 측정 실수를 줄여주게끔 도와준다고 합니
저는 Android Jetpack의 Benchmark 라이브러가 나온지 꽤 오래되었을 줄 알았지만 2019년에 Google I/O에서 정식으로 처음 소개되었다고 합니다.
생각보다 발표 된지 오래 되지 않았네요
해당 발표 영상 링크입니다. benchmark 공부를하면서 많은 도움을 받았습니다.
이전에 Jetpack Benchmark가 나오기 전에는 어떻게 성능 측정을 진행하였을까요?
바로 아래 그림처럼 System.nanoTime() 함수를 사용하여 시작 시간과 끝 시간 사이의 차이를 기록하며 성능을 측정하였습니다.
이러한 방법은 여러 번 반복 실행시 각각 다르게 측정될 수 있는 변동성, 배경 작업으로 인한 노이즈, 그리고 자원 사용량에 따른 성능 차이 등의 단점들이 있습니다.
즉 성능측정은 일관성이 중요한데 그러한 일관성이 보장이 안되는 것이죠.
똑같은 코드에 대해서 어떤 측정에는 1초 걸리고 또 다른 측정에서는 10초가 걸리는 상황이 발생할 수 있다는 것입니다.
이러한 점을 보안하고자 Jetpack Benchmark를 사용하여 작업의 반복 실행, 배경 작업의 최소화, CPU 및 메모리 최적화 등의 최적화된 기능들과 함께 보다 정확하고 일관된 성능 측정이 가능해집니다.
코드도 훨씬 간략해졌죠. 그 이유는 Benchmark 라이브러리가 내부적으로 반복 실행, 초기화, 종료 등의 작업을 이미 구현 해 놓았기 때문입니다.
최적의 결과를 위해 실제 디바이스에서 테스트를 실행하는 것이 좋습니다. 에뮬레이터에서는 정확한 성능 측정이 어려울 수 있습니다. 백그라운드 작업, 네트워크 연결 상태, 배터리 상태 등 외부 요인의 영향을 최소화하여 테스트하는 것이 좋습니다. 측정할 때는 Release 버전과 흡사한 환경으로 설정해주면 좋다고합니다.
즉 benchmark는 실제 릴리스 앱 상태에서 실제 디바이스에서 측정하는 것이 일반적이라고 합니다.
debuggable="false"
codeCoverageEnabled="false"
이렇게 하는 이유는 Debug 모드에서 애플리케이션은 여러 추가적인 디버깅 정보와 기능을 포함하고 있습니다. 이는 성능에 추가적인 오버헤드를 발생시킬 수 있습니다.
코드 커버리지는 테스트가 코드의 어느 부분을 실행하는지 측정하는 기능인데요 이 기능 또한 추가적인 성능 오버헤드를 발생시키므로, 성능 측정 시에는 비활성화하는 것이 좋습니다.
Proguard와 R8은 코드 축소, 최적화, 난독화를 수행하는 도구입니다 이 도구들을 사용하면 불필요한 코드가 제거되고, 실행 성능이 향상될 수 있습니다 실제 Release 버전의 앱에서는 이 도구들을 사용하기 때문에, 벤치마크 테스트에서도 이와 유사한 환경을 만들기 위해 활성화하는 것이 좋습니다.
이러한 설정들을 통해 벤치마크 테스트는 실제 사용자들에게 제공될 최종 애플리케이션의 성능에 가까운 환경에서 정확한 성능 측정을 수행할 수 있게 됩니다
구글 I/O 영상에서는 아래 그래프 그림으로 설명합니다.
이 그래프는 debuggable
속성의 값을 true
로 설정했을 때와 false
로 설정했을 때의 성능 차이를 나타냅니다.
debuggable=true
로 설정했을 때 성능 측정 결과가 더 나쁜 것으로 나타납니다.debuggable=false
로 설정하는 것이 바람직합니다."Ramp" 현상은 CPU의 클록 주파수가 초기에 낮은 상태에서 시작하여 시간에 따라 점진적으로 증가하는 현상을 의미합니다.
Clocks start low, ramp slowly:
이러한 초기에 CPU 클록 속도를 낮게 유지하는 것은 에너지 소비 측면에서 디바이스 효율적이지만 벤치마크를 할때 치명적으로 작용할 수 있습니다.
따라서 영상에서는 아래를 설명합니다.
Benchmark runs warmup to account for this:
Spins loop for minimum 250ms:
Starts measuring once perf stabilizes:
Also handles Android Runtime’s Just-In-Time compilation:
영상에서는 램핑 보다 큰 문제인 Dive에 대해서 언급합니다.
When device gets hot, clocks dive quickly
디바이스가 뜨거워지면 클럭 속도가 빠르게 감소한다고 설명하고 있습니다
이는 대부분의 모바일 디바이스에서 발생하는 현상으로, 디바이스가 과도하게 열을 발생할 때 내부적으로 손상을 방지하고 안정성을 유지하기 위해 자동으로 클럭 속도를 줄이는 메커니즘이 작동하기 때문입니다.
영상에서는 이 Dive의 치명적인 부분을 그래프로 설명합니다
그래프를 통해 볼 수 있는 주요 포인트는 디바이스가 작업을 계속 수행하면서 열이 발생하게 되면, 그로 인해 디바이스의 성능이 저하되고 이로인해 작업을 수행하는 시간이 급격하게 늘어난다는 것을 보여줍니다.
이러한 현상도 디바이스를 보호하기 위해서 필수적인 매커니즘이지만 밴치마크에서 정확성에 치명적입니다.
또한 과열을 방지하기 위해 성능을 자동으로 제한하는 "Thermal throttling" 현상을 Benchamrk에서 제어하는 법을 설명합니다.
세가지 전략에 대해 설명합니다.
1. Lock clocks
2. Sustained perf
3. Thread.sleep()
이는 프로그래밍에서 사용되는 함수로, 실행 중인 스레드를 일정 시간동안 일시 정지시키는 기능을 합니다. 이를 통해 CPU의 부하를 줄이거나, 기기의 온도를 안정화시킬 수 있습니다.
영상에서는 Thread.sleep이 일반적인 기기에서 사용할 수 있는 일반적인 방법이라고 언급합니다.
이 전략들은 성능 저하나 과열과 같은 문제를 최소화하고 기기의 안정성을 유지하기 위해 사용됩니다.
벤치마크 테스트를 최적의 조건에서 실행하기 위한 또 다른 전략으로 'Foreground' 활성화를 언급합니다.
성능 테스트를 할 때 앱을 사용자가 계속 볼 수 있게 (즉, Foreground에서) 실행하는 이유는 여러 가지가 있습니다:
'Contention'은 여러 프로세스나 스레드가 동시에 특정 자원(예: CPU, 메모리)에 액세스하려고 할 때 발생하는 경쟁 상황을 의미합니다.
"Everything is a shared resource"는 모든 자원이 여러 작업 간에 공유되기 때문에 contention이 발생할 수 있다는 것을 나타냅니다.
위에 그림을 보시면 특정 Loop에서 BG WORK(다른 작업이나 프로세스에 의한 BackGround Work)가 발생하여 시간 지연이 발생하는 것을 보여줍니다.
이러한 특정 Loop에서 Contention으로 인한 시간 지연을 해결하기 위한 전력으로 벤치마크를 여러 번 수행한 후 결과를 한 번만 보고하는 것이 좋다고 합니다.
"Most contention is for a short duration"는 대부분의 contention이 잠깐 발생한다는 것을 의미합니다. 이로 인해 특정 Loop는 영향을 받지만 특정 Loop에서는 Contention에 노출이되지않습니다.
따라서 Iteration을 여러번으로 주는 것이 좋고 분석할 때는 "Report and track minimum, not average"을 하라고 합니다.
이 말은 평균 대신 최소값을 보고하고 추적해야 한다는 것을 의미합니다. 이는 contention으로 인한 극단적인 성능 저하를 반영하기 위한 것입니다
Benchmark에서는 평균값이 아니라 중앙값을 사용하던데 이러한 이유 때문이라고 생각이 듭니다.
전통적인 벤치마킹은 여러 변수, 예를 들면 Clock stability와 Background interference 등을 고려해야하기 때문에 정확한 성능 측정이 복잡하고 어려움이 많았다고 합니다.
이러한 복잡성을 해결하기 위해 Jetpack Benchmark가 개발되었습니다. 이 도구는 다음과 같은 주요 특징을 갖고 있습니다:
2019 구글 I/O Benchmark 설명 및 공식 문서를 참고하여 작성하였습니다!!
Benchmark를 적용하는 자세한 방법은 공식문서를 참고해주세요.
최신 release 소식은 Link에서 확인할 수 있습니다.
다음 글은 실제 제 프로젝트에 Benchmark 적용과 Baseline-profile에 대해 준비하겠습니다!