perf(android): Speed up ViewUtils.findTarget by reducing allocations#5594
Draft
runningcode wants to merge 2 commits into
Draft
perf(android): Speed up ViewUtils.findTarget by reducing allocations#5594runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
Replace the per-gesture LinkedList BFS queue with an ArrayDeque in ViewUtils.findTarget and ComposeGestureTargetLocator, and drop the intermediate list allocated when enqueuing Compose children. LinkedList allocates a node object per element on every tap and scroll start; ArrayDeque is array-backed and allocates almost nothing per element. Traversal behavior is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 18c0bc2 | 306.73 ms | 349.77 ms | 43.03 ms |
| 0eaac1e | 316.82 ms | 357.34 ms | 40.52 ms |
| d15471f | 303.49 ms | 439.08 ms | 135.59 ms |
| fc5ccaf | 276.52 ms | 370.46 ms | 93.93 ms |
| e2dce0b | 308.96 ms | 360.10 ms | 51.14 ms |
| 5b1a06b | 352.27 ms | 413.70 ms | 61.43 ms |
| 37ec571 | 366.04 ms | 424.28 ms | 58.23 ms |
| 9fbb112 | 361.43 ms | 427.57 ms | 66.14 ms |
| bbc35bb | 324.88 ms | 425.73 ms | 100.85 ms |
| ff8eea4 | 313.42 ms | 337.08 ms | 23.66 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 18c0bc2 | 1.58 MiB | 2.13 MiB | 557.33 KiB |
| 0eaac1e | 1.58 MiB | 2.19 MiB | 619.17 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| fc5ccaf | 1.58 MiB | 2.13 MiB | 557.54 KiB |
| e2dce0b | 0 B | 0 B | 0 B |
| 5b1a06b | 0 B | 0 B | 0 B |
| 37ec571 | 0 B | 0 B | 0 B |
| 9fbb112 | 1.58 MiB | 2.11 MiB | 539.18 KiB |
| bbc35bb | 1.58 MiB | 2.12 MiB | 553.01 KiB |
| ff8eea4 | 1.58 MiB | 2.28 MiB | 718.64 KiB |
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
Replace the per-gesture
LinkedListBFS queue with anArrayDequeinViewUtils.findTargetandComposeGestureTargetLocator, and drop the intermediate list allocated when enqueuing Compose children.ViewUtils.findTargetruns on every tap (onSingleTapUp) and at the start of every scroll (onScroll), traversing the view tree to locate the gesture target. The traversal used aLinkedListas its queue, which allocates a node object for everyadd/poll— i.e. one allocation per visited view, on the main thread, on every gesture.ArrayDequeis array-backed and allocates almost nothing per element. The Compose locator had the sameLinkedListpattern plus an extra intermediateListallocated per node via.asMutableList().map { }.Traversal order and target-selection logic are unchanged — this is an allocation/GC-pressure reduction only, not a behavior change.
💡 Motivation and Context
JAVA-534 / #5481. Reduce avoidable main-thread allocations in the gesture target traversal hot path.
Note on scope: profiling on a Pixel 3 (Android 12) shows the dominant CPU cost in
findTargetisgetLocationOnScreen(called per visited view), not the queue allocation. This PR is the low-risk allocation cleanup; thegetLocationOnScreencost is tracked as a separate follow-up.💚 How did you test it?
Existing
ViewUtilsTestandSentryGestureListenerunit tests pass. Verified on-device (Pixel 3, Android 12) that gesture target resolution is unchanged — scroll target still resolves correctly (Scroll target found: scrolling_container).📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Separate PR: replace the per-view
getLocationOnScreen(O(N·depth)) with point-transform-during-descent (O(N)), the actual CPU hotspot in this path.