MessageQueue까지, 왜 필요한지 기반 개념 Main Thread Legacy MessageQueue synchronized lock 공유와 lock contention 문제 Android 17 · DeliQueue Message Google I/O Extended ’26 등록과 실행 순서 관리의 책임 분리 5
하나의 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
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
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
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
T1 동기화 구간 Background Tk 대기 동기화 구간 Background TN 대기 대기 동기화 구간 Main Thread 대기 대기 대기 enqueue t₀ t₁ t₂ t₃ 한 시점에 동기화 구간은 하나 — Main은 앞 순번이 끝나야 enqueue → UI Jank Google I/O Extended ’26 22
· 빈 힙에 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
· 이미 채워진 힙에 병합 → 새 메시지가 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
실행할 메시지 (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
새 메시지 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
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
그 비용은 매우 작고, 대신 공유 lock 대기가 사라졌습니다. ~35ns 이어도 은 최대 약 7번 최적화 N=100 sift-down · branchless 비교 Google I/O Extended ’26 0.0002% 프레임 예산 대비 — 사실상 무시 가능한 수준 16ms 공유 lock 대기 제거 경로가 물던 대기 비용이 사라짐 Producer 45
가능한 성능을 얻었다 포기한 것 엄격한 when 순서 보장 완화 — bulk transfer 도중 push된 메시지는 한 사이클(수 µs) 뒤에 처리됩니다. Google I/O Extended ’26 얻은 것 완전 제거 · priority inversion 원천 차단 모든 삽입이 O(1), 절대 블로킹 없음. lock contention · 46