Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android 17의 Lock-Free MessageQueue: 기존 문제점과 Del...

Android 17의 Lock-Free MessageQueue: 기존 문제점과 DeliQueue 해결 과정

Avatar for Daekyu

Daekyu

July 25, 2026

More Decks by Daekyu

Other Decks in Programming

Transcript

  1. Google Extended 의 Android 17 Lock-Free MessageQueue 기존 문제점과 DeliQueue

    해결 과정 발표 자료 Google I/O Extended ’26 강대규 당근 Android 17 · MessageQueue
  2. 내부 구현 변경 MessageQueue AOSP에 DeliQueue 구현이 추가됐고, Android 17에서는

    Legacy와 함께 선택 가능한 내부 경로로 통합됐습니다. AOSP · 934102f · DeliQueue implementation 2
  3. MessageQueue 변경의 성능 개선 효과 측정 방식과 조건이 서로 다르며,

    일반 앱 모두에 동일하게 적용되는 수치는 아닙니다 Google I/O 2026 · What’s new in Android 3
  4. 목차 01 02 03 Handler · Looper · MessageQueue 부터

    MessageQueue까지, 왜 필요한지 기반 개념 Main Thread Legacy MessageQueue synchronized lock 공유와 lock contention 문제 Android 17 · DeliQueue Message Google I/O Extended ’26 등록과 실행 순서 관리의 책임 분리 5
  5. 부재 시 대기 Message Native Poll 반복 확인하지 않고 다음

    이벤트까지 OS에 대기를 맡깁니다. Google I/O Extended ’26 12
  6. 의 두 가지 구조적 문제 Legacy MessageQueue 두 문제 모두

    하나의 synchronized lock을 공유하는 구조에서 출발합니다. ① Lock Contention 모든 Producer가 하나의 lock을 두고 경쟁해, Main Thread의 enqueue와 UI Looper의 next까지 함께 대기합니다. Google I/O Extended ’26 ② Priority Inversion 낮은 Priority가 lock을 쥔 사이 중간 Priority가 CPU를 선점해, 높은 Priority의 UI가 간접적으로 지연됩니다. 14
  7. 의 synchronized(this) 범위 enqueueMessage // enqueueMessage — Legacy boolean enqueueMessage(Message

    msg, long when) { synchronized (this) { Message p = mMessages; while (p.next != null && p.next.when <= when) { p = p.next; } msg.next = p.next; p.next = msg; } } Google I/O Extended ’26 15
  8. when 오름차순으로 정렬된 LinkedList synchronized (this) { Message p =

    mMessages; while (p.next != null && p.next.when <= when) { p = p.next; } msg.next = p.next; p.next = msg; } 는 head(A)에서 시작 — 새 Message(msg)는 아직 Queue에 미연결 p Google I/O Extended ’26 16
  9. when 비교를 통한 삽입 위치 탐색 synchronized (this) { Message

    p = mMessages; while (p.next != null && p.next.when <= when) { p = p.next; } msg.next = p.next; p.next = msg; } 부터 when을 비교하며 msg가 들어갈 위치를 탐색 head Google I/O Extended ’26 17
  10. 와 C 사이의 새 Message 연결 B synchronized (this) {

    Message p = mMessages; while (p.next != null && p.next.when <= when) { p = p.next; } msg.next = p.next; p.next = msg; } msg.next = C, 안 그다음 B.next = msg — 두 참조 변경도 같은 lock Google I/O Extended ’26 18
  11. O(N) 탐색과 함께 늘어나는 lock 보유 시간 synchronized (this) {

    Message p = mMessages; while (p.next != null && p.next.when <= when) { p = p.next; } msg.next = p.next; p.next = msg; } 최악의 경우 O(N) 탐색 + 링크 변경까지 같은 lock 안에서 실행 Google I/O Extended ’26 19
  12. Legacy · 문제 ① Lock Contention 하나의 공유 lock을 두고

    모든 Producer가 경쟁합니다 Google I/O Extended ’26 20
  13. 공유 lock 획득을 기다리는 Producer Lock contention = Google I/O

    Extended ’26 여러 Thread가 같은 lock을 동시에 획득하려는 상태 21
  14. 공유 lock으로 직렬화된 enqueue 각 Thread의 동기화 구간 실행 Background

    T1 동기화 구간 Background Tk 대기 동기화 구간 Background TN 대기 대기 동기화 구간 Main Thread 대기 대기 대기 enqueue t₀ t₁ t₂ t₃ 한 시점에 동기화 구간은 하나 — Main은 앞 순번이 끝나야 enqueue → UI Jank Google I/O Extended ’26 22
  15. Legacy · 문제 ② Priority Inversion 낮은 Priority가 lock을 쥐고,

    높은 Priority가 밀려납니다 Google I/O Extended ’26 24
  16. 등록과 실행순서 관리의 책임 분리 Android 17 · DeliQueue Message

    는 빠르게 등록하고, Looper가 실행 순서를 정합니다 Producer Google I/O Extended ’26 32
  17. Bulk Transfer Bulk Transfer ① — 빈 Min-Heap 적재 ①

    · 빈 힙에 ush → when 순으로 정렬 Looper Treiber Stack top D C B A w:5 w:15 w:9 w:2 전용 Min-Heap A 전부 순회 → insert w:2 D C w:5 w:15 B w:9 스택 순서 D·C·B·A (when 5·15·9·2) → 힙은 when 기준 재배치 · root = 가장 이른 A(2) · Heap 변경은 Looper 전용 Google I/O Extended ’26 40
  18. Bulk Transfer Bulk Transfer ② — 기존 Min-Heap 병합 ②

    · 이미 채워진 힙에 병합 → 새 메시지가 when 위치로 병합된 Min-Heap · 보라=새 · 회색=기존 기존 힙 · 아직 안 온 미래 메시지 P Q w:7 w:12 A 새로 push된 메시지 · Stack (top→D·C·B·A) when D C B A w:5 w:15 w:9 w:2 병합 w:2 위치로 insert D P w:5 w:7 B Q C w:9 w:12 w:15 새 메시지(A·D·B·C)는 각자 when 위치로 삽입 — 기존(P·Q)과 섞여 재정렬 · root = 전체에서 가장 이른 A(2) Google I/O Extended ’26 41
  19. Looper 전용 Min-Heap 규칙 Min-Heap · root = 가장 먼저

    실행할 메시지 (when 최소) 배열로 저장 A w:2 A D C B w:2 w:5 w:15 w:9 1 2 3 D C 0 w:5 w:15 index i 부모 ≤ 자식 B 의 부모 = ⌊(i−1)/2⌋ 포인터 없이 배열 인덱스만으로 부모·자식 계산 2≤5 · 2≤15 · 5≤9 w:9 이 가장 작은 메시지 = 다음에 실행할 것 (A · w:2) → next()가 O(1)로 확인 root = when Google I/O Extended ’26 42
  20. Min-Heap 삽입 — append 후 sift-up 삽입 · 기존 힙에

    새 메시지 E(3) 추가 → si -up 정렬 전 · E를 끝에 append 정렬 후 · E↔D 교환 A A w:2 w:2 si -up ↑ D C E C w:5 w:15 w:3 w:15 B E B D w:9 w:3 w:9 w:5 새 E(3)가 부모 D(5)보다 이르다 정렬 완료 · A(2) ≤ E(3) 새 메시지는 배열 끝에 넣고, 부모보다 이르면 위로 올린다 · O(log N) Google I/O Extended ’26 43
  21. Min-Heap dispatch — root next() · root(A) root 꺼냄 ·

    next() 제거 후 sift-down 꺼내고 si -down 정렬 전 · 마지막 D를 root로 정렬 후 · E↔D 교환 D E w:5 w:3 A 로 Handler si -down ↓ E C D C w:3 w:15 w:5 w:15 B B w:9 w:9 가 자식 E(3)보다 늦다 root D(5) 정렬 완료 · E(3)가 새 root 를 dispatch → 마지막을 root로 → 더 이른 자식과 아래로 · O(log N) · main thread ~35ns root Google I/O Extended ’26 44
  22. 로 이동한 정렬 비용 Main Thread 정렬(sift-down)은 Main Thread에서 실행되지만

    그 비용은 매우 작고, 대신 공유 lock 대기가 사라졌습니다. ~35ns 이어도 은 최대 약 7번 최적화 N=100 sift-down · branchless 비교 Google I/O Extended ’26 0.0002% 프레임 예산 대비 — 사실상 무시 가능한 수준 16ms 공유 lock 대기 제거 경로가 물던 대기 비용이 사라짐 Producer 45
  23. 의 의도된 트레이드오프 DeliQueue 엄격한 when 순서를 조금 완화하고, 예측

    가능한 성능을 얻었다 포기한 것 엄격한 when 순서 보장 완화 — bulk transfer 도중 push된 메시지는 한 사이클(수 µs) 뒤에 처리됩니다. Google I/O Extended ’26 얻은 것 완전 제거 · priority inversion 원천 차단 모든 삽입이 O(1), 절대 블로킹 없음. lock contention · 46
  24. 비용 이동과 Tail Latency 개선 연산 LEGACY DELIQUEUE Producer Message

    등록 O(N) + lock amortized O(1) Heap insert/remove — O(log N) 조건 삭제 O(N) O(N) + Source · Android Developers Blog 정리 47