Android 런타임 (ART)팀은 컴파일된 코드나 최대 메모리 회귀를 저해하지 않고 컴파일 시간을 18% 단축했습니다. 이 개선사항은 메모리 사용량이나 컴파일된 코드의 품질을 저하시키지 않으면서 컴파일 시간을 개선하기 위한 2025년 이니셔티브의 일환으로 진행되었습니다.
컴파일 시간 속도를 최적화하는 것은 ART에 매우 중요합니다. 예를 들어 적시 (JIT) 컴파일은 애플리케이션의 효율성과 전반적인 기기 성능에 직접적인 영향을 미칩니다. 컴파일 속도가 빠르면 최적화가 시작되기 전의 시간이 줄어들어 사용자 환경이 더 원활하고 반응성이 높아집니다. 또한 JIT와 AOT 모두 컴파일 시간 속도가 개선되면 컴파일 프로세스 중에 리소스 소비가 줄어들어 특히 하위 엔드 기기에서 배터리 수명과 기기 열에 도움이 됩니다.
이러한 컴파일 시간 속도 개선사항 중 일부는 2025년 6월 Android 출시에서 출시되었으며 나머지는 연말 Android 출시에서 제공될 예정입니다. 또한 버전 12 이상의 모든 Android 사용자는 Mainline 업데이트를 통해 이러한 개선사항을 받을 수 있습니다.
최적화 컴파일러 최적화
컴파일러를 최적화하는 것은 항상 트레이드오프의 문제입니다. 속도를 무료로 얻을 수는 없습니다. 무언가를 포기해야 합니다. Google은 컴파일러를 더 빠르게 만들되 메모리 회귀를 도입하지 않고, 무엇보다도 생성되는 코드의 품질을 저하시키지 않는다는 매우 명확하고 어려운 목표를 설정했습니다. 컴파일러가 더 빠르지만 앱이 더 느리게 실행되면 실패한 것입니다.
이러한 엄격한 기준을 충족하는 영리한 솔루션을 깊이 파고들어 조사하고 찾는 데는 자체 개발 시간을 기꺼이 투자했습니다. 개선할 영역을 찾고 다양한 문제에 적합한 해결 방법을 찾는 방법을 자세히 살펴보겠습니다.
가치 있는 최적화 가능성 찾기
측정항목을 최적화하려면 먼저 측정항목을 측정할 수 있어야 합니다. 그렇지 않으면 개선되었는지 여부를 확인할 수 없습니다. 다행히도 변경 전후에 측정하는 데 사용하는 기기를 동일하게 사용하고 기기가 온도로 인해 성능을 제한하지 않도록 하는 등 몇 가지 예방 조치를 취하면 컴파일 시간 속도는 상당히 일관됩니다. 또한 컴파일러 통계와 같은 결정론적 측정항목도 있어 내부에서 어떤 일이 일어나고 있는지 파악할 수 있습니다.
이러한 개선을 위해 희생한 리소스는 개발 시간이었으므로 최대한 빠르게 반복할 수 있기를 원했습니다. 즉, 솔루션을 프로토타입으로 만들기 위해 대표적인 앱 (퍼스트 파티 앱, 서드 파티 앱, Android 운영체제 자체의 혼합)을 몇 개 가져왔습니다. 나중에 광범위한 수동 및 자동 테스트를 통해 최종 구현이 가치가 있는지 확인했습니다.
엄선된 APK 세트를 사용하여 로컬에서 수동 컴파일을 트리거하고, 컴파일 프로필을 가져오고, pprof를 사용하여 시간을 소비하는 위치를 시각화합니다.
pprof의 프로필 flame 그래프 예시
pprof 도구는 매우 강력하며 데이터를 슬라이스, 필터링, 정렬하여 예를 들어 어떤 컴파일러 단계나 메서드가 가장 많은 시간을 차지하는지 확인할 수 있습니다. pprof 자체에 대해서는 자세히 설명하지 않겠습니다. 막대가 클수록 컴파일에 더 많은 시간이 걸린다는 점만 알아두세요.
이러한 뷰 중 하나는 '하향식' 뷰로, 어떤 메서드가 대부분의 시간을 차지하는지 확인할 수 있습니다. 아래 이미지에서 컴파일 시간의 1% 이상을 차지하는 Kill이라는 메서드를 확인할 수 있습니다. 다른 인기 방법도 블로그 게시물 후반부에서 다룰 예정입니다.
프로필의 하향식 보기
최적화 컴파일러에는 전역 값 번호 지정 (GVN)이라는 단계가 있습니다. 전체적으로 어떤 작업을 하는지는 걱정하지 않아도 되지만, 필터에 따라 일부 노드를 삭제하는 `Kill` 이라는 메서드가 있다는 점을 알아두면 됩니다. 모든 노드를 반복하고 하나씩 확인해야 하므로 시간이 오래 걸립니다. 이 시점에 활성 상태인 노드와 관계없이 검사가 거짓임이 미리 알려진 사례가 있습니다. 이 경우 반복을 완전히 건너뛰어 1.023% 에서 ~0.3% 로 낮추고 GVN의 런타임을 ~15% 개선할 수 있습니다.
가치 있는 최적화 구현
시간이 어디에 사용되는지 측정하고 감지하는 방법을 살펴봤지만 이는 시작에 불과합니다. 다음 단계는 컴파일에 소요되는 시간을 최적화하는 방법입니다.
일반적으로 위의 `Kill` 과 같은 경우 노드를 반복하는 방법을 살펴보고 병렬로 작업을 실행하거나 알고리즘 자체를 개선하는 등의 방법으로 더 빠르게 실행합니다. 사실 처음에는 그렇게 시도했는데, 할 일을 찾을 수 없을 때만 '잠깐만...'이라는 생각이 들었고 해결책은 (경우에 따라) 전혀 반복하지 않는 것이었습니다. 이러한 종류의 최적화를 수행할 때는 전체를 보지 못하고 세부사항에만 집중하기 쉽습니다.
다른 경우에는 다음과 같은 다양한 기법을 사용했습니다.
- 휴리스틱을 사용하여 최적화가 가치 있는 결과를 생성하지 못하므로 건너뛸 수 있는지 여부를 결정
- 계산된 데이터를 캐시하기 위해 추가 데이터 구조 사용
- 속도 향상을 위해 현재 데이터 구조 변경
- 경우에 따라 순환을 방지하기 위해 결과를 지연 계산
- 올바른 추상화 사용 - 불필요한 기능은 코드 속도를 저하시킬 수 있음
- 많은 로드를 통해 자주 사용되는 포인터를 추적하지 않음
최적화를 추구할 가치가 있는지 어떻게 알 수 있나요?
다행히 그럴 필요가 없습니다. 영역에서 컴파일 시간이 많이 소요되는 것을 감지하고 이를 개선하기 위해 개발 시간을 할애한 후에도 해결책을 찾을 수 없는 경우가 있습니다. 아무것도 할 수 없거나, 구현하는 데 너무 오래 걸리거나, 다른 측정항목이 크게 회귀하거나, 코드베이스 복잡성이 증가하는 등의 이유가 있을 수 있습니다. 이 블로그 게시물에서 확인할 수 있는 성공적인 최적화 외에도 수많은 최적화가 실현되지 않았다는 점을 알아두세요.
이와 비슷한 상황이라면 최소한의 작업으로 측정항목을 얼마나 개선할 수 있는지 추정해 보세요. 이는 다음을 의미합니다.
- 이미 수집한 측정항목을 사용하거나 직감으로 추정
- 빠르고 간단한 프로토타입으로 추정
- 솔루션을 구현합니다.
솔루션의 단점을 추정하는 것도 고려해야 합니다. 예를 들어 추가 데이터 구조를 사용하려는 경우 얼마나 많은 메모리를 사용할 수 있나요?
자세히 살펴보기
바로 구현된 변경사항을 살펴보겠습니다.
FindReferenceInfoOf라는 메서드를 최적화하는 변경사항을 구현했습니다. 이 메서드는 항목을 찾기 위해 벡터의 선형 검색을 수행했습니다. FindReferenceInfoOf가 O(n)이 아닌 O(1)이 되도록 명령의 ID로 색인이 생성되도록 데이터 구조를 업데이트했습니다. 또한 크기 조절을 방지하기 위해 벡터를 사전 할당했습니다. 벡터에 삽입된 항목 수를 계산하는 추가 필드를 추가해야 했기 때문에 메모리가 약간 증가했지만, 최대 메모리가 증가하지 않았기 때문에 작은 희생이었습니다. 이렇게 하면 LoadStoreAnalysis 단계가 34~66% 빨라져 컴파일 시간이 0.5~1.8% 개선됩니다.
여러 곳에서 사용하는 HashSet의 맞춤 구현이 있습니다. 이 데이터 구조를 만드는 데 상당한 시간이 걸렸고 그 이유를 알게 되었습니다. 몇 년 전 이 데이터 구조는 매우 큰 HashSet을 사용하는 몇 군데에서만 사용되었으며 이를 위해 최적화되도록 조정되었습니다. 하지만 요즘에는 항목이 몇 개 없고 수명이 짧은 반대 방향으로 사용되었습니다. 즉, 이 거대한 HashSet을 만들면서 사이클을 낭비했지만 몇 개의 항목에만 사용한 후 삭제했습니다. 이 변경사항으로 컴파일 시간이 약 1.3~2% 개선되었습니다. 이전만큼 큰 데이터 구조를 사용하지 않으므로 메모리 사용량이 약 0.5~1% 감소했습니다.
람다에 참조로 데이터 구조를 전달하여 복사하지 않도록 함으로써 컴파일 시간을 0.5~1% 개선했습니다. 이는 원래 검토에서 누락되어 수년 동안 코드베이스에 있었습니다. pprof에서 프로필을 살펴본 덕분에 이러한 메서드가 많은 데이터 구조를 생성하고 소멸시킨다는 것을 알게 되었고, 이를 조사하고 최적화할 수 있었습니다.
계산된 값을 캐싱하여 컴파일된 출력을 쓰는 단계를 가속화했으며, 이는 총 컴파일 시간의 1.3~2.8% 에 해당합니다. 하지만 추가된 장부 기록이 너무 많았고 자동 테스트에서 메모리 회귀를 알렸습니다. 나중에 동일한 코드를 다시 살펴보고 메모리 회귀를 처리할 뿐만 아니라 컴파일 시간을 추가로 ~0.5~1.8% 개선하는 새 버전을 구현했습니다. 이 두 번째 변경사항에서는 두 데이터 구조 중 하나를 없애기 위해 이 단계의 작동 방식을 리팩터링하고 재구상해야 했습니다.
최적화 컴파일러에는 더 나은 성능을 위해 함수 호출을 인라인하는 단계가 있습니다. 인라인할 메서드를 선택하기 위해 계산을 수행하기 전의 휴리스틱과 작업을 수행한 후 인라인을 완료하기 직전의 최종 검사를 모두 사용합니다. 이러한 검사 중 하나에서 인라인이 가치가 없다고 감지되면 (예: 너무 많은 새 명령어가 추가됨) 메서드 호출을 인라인하지 않습니다.
시간이 많이 소요되는 계산을 실행하기 전에 인라인이 성공할지 여부를 추정하기 위해 '최종 확인' 카테고리의 두 가지 검사를 '휴리스틱' 카테고리로 이동했습니다. 추정치이므로 완벽하지는 않지만, 새로운 휴리스틱이 성능에 영향을 주지 않고 이전에 인라인된 항목의 99.9% 를 포함하는 것으로 확인되었습니다. 이러한 새로운 휴리스틱 중 하나는 필요한 DEX 레지스터에 관한 것이었고 (0.2~1.3% 개선) 다른 하나는 명령어 수에 관한 것이었습니다 (2% 개선).
여러 곳에서 사용하는 BitVector의 맞춤 구현이 있습니다. 크기 조절 가능한 BitVector 클래스가 특정 고정 크기 비트 벡터의 더 간단한 BitVectorView로 대체되었습니다. 이렇게 하면 일부 간접 및 런타임 범위 검사가 제거되고 비트 벡터 객체 생성이 빨라집니다.
또한 BitVectorView 클래스는 항상 uint32_t를 이전 BitVector로 사용하는 대신 기본 스토리지 유형에서 템플릿화되었습니다. 이를 통해 Union()과 같은 일부 작업이 64비트 플랫폼에서 두 배 많은 비트를 함께 처리할 수 있습니다. 영향을 받는 함수의 샘플이 Android OS를 컴파일할 때 총 1% 이상 감소했습니다. 이 작업은 여러 변경사항에 걸쳐 진행되었습니다[1, 2, 3, 4, 5, 6].
모든 최적화에 대해 자세히 설명하면 하루 종일 걸릴 것입니다. 추가 최적화에 관심이 있다면 Google에서 구현한 다른 변경사항을 살펴보세요.
- 회계 추가로 컴파일 시간을 약 0.6~1.6% 개선
- 가능하면 데이터를 지연 계산하여 주기를 방지하세요.
- 사용하지 않을 때는 사전 컴퓨팅 작업을 건너뛰도록 코드를 리팩터링합니다.
- 할당자를 다른 곳에서 쉽게 가져올 수 있는 경우 일부 종속 로드 체인 방지
- 불필요한 작업을 방지하기 위해 검사를 추가하는 또 다른 사례입니다.
- 레지스터 할당자에서 레지스터 유형 (코어/FP)에 빈번한 브랜치 방지
- 컴파일 시간에 일부 배열이 초기화되었는지 확인합니다. clang에 의존하지 마세요.
- 일부 루프 정리 clang이 더 잘 최적화할 수 있는 범위 루프를 사용하세요. 루프 부작용으로 인해 컨테이너의 내부 포인터를 다시 로드할 필요가 없기 때문입니다. 각 입력에 대해 인라인 처리된 `InputAt(.)` 을 통해 루프에서 가상 함수 `HInstruction::GetInputRecords()` 를 호출하지 마세요.
- 컴파일러 최적화를 활용하여 방문자 패턴에 Accept() 함수를 사용하지 마세요.
결론
ART의 컴파일 시간 속도를 개선하기 위한 노력으로 상당한 개선이 이루어져 Android가 더 유연하고 효율적으로 작동할 뿐만 아니라 배터리 수명과 기기 열 관리에도 도움이 됩니다. 최적화를 부지런히 식별하고 구현함으로써 메모리 사용량이나 코드 품질을 저하시키지 않고도 상당한 컴파일 시간 이득을 얻을 수 있음을 입증했습니다.
이 여정에는 pprof와 같은 도구를 사용한 프로파일링, 반복 의지, 때로는 덜 유익한 방법을 포기하는 것도 포함되었습니다. ART 팀의 공동 노력으로 컴파일 시간이 상당한 비율로 단축되었을 뿐만 아니라 향후 발전을 위한 기반도 마련되었습니다.
이러한 개선사항은 모두 2025년 연말 Android 업데이트에서 제공되며, Mainline 업데이트를 통해 Android 12 이상에서 사용할 수 있습니다. Google의 최적화 프로세스에 대한 자세한 내용을 통해 컴파일러 엔지니어링의 복잡성과 보상에 대한 유용한 정보를 얻으셨기를 바랍니다.
계속 읽기
-
제품 소식
모든 개발자의 AI 워크플로와 요구사항은 고유하며, AI가 개발에 어떤 도움을 줄지 선택할 수 있는 것이 중요합니다. 1월에는 Android 스튜디오에서 AI 기능을 구동하는 데 로컬 또는 원격 AI 모델을 선택할 수 있는 기능이 도입되었습니다.
Matthew Warner • 전문 길이: 2분
-
제품 소식
이제 Android 스튜디오 Panda 3가 안정화되어 프로덕션에서 사용할 수 있습니다. 이번 출시를 통해 AI 기반 워크플로를 더욱 세부적으로 제어하고 맞춤설정할 수 있어 고품질 Android 앱을 그 어느 때보다 쉽게 빌드할 수 있습니다.
Matt Dyor • 3분 읽기
-
제품 소식
Google은 가장 강력한 AI 모델을 주머니 속 Android 기기에 직접 제공하기 위해 최선을 다하고 있습니다. 오늘 최신 최첨단 개방형 모델인 Gemma 4의 출시를 발표하게 되어 매우 기쁩니다.
Caren Chang, David Chou • 3분 읽기
소식 받아 보기
Android 개발 관련 최신 정보를 이메일로 받아 보세요.