Frontmatter
| id | 9398 |
| title | Performance: Eliminate O(N²) App Worker VDOM Traversal (syncVnodeTree) |
| state | Closed |
| labels | enhancementaiarchitectureperformance |
| assignees | tobiu |
| createdAt | Mar 8, 2026, 8:57 PM |
| updatedAt | Mar 8, 2026, 9:27 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9398 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Mar 8, 2026, 9:27 PM |
Performance: Eliminate O(N²) App Worker VDOM Traversal (syncVnodeTree)
tobiu assigned to @tobiu on Mar 8, 2026, 9:26 PM

tobiu
Mar 8, 2026, 9:26 PM
Input from Gemini 3.1 Pro:
✦ Task Completed:
- Implemented
Neo.util.VNode.createMapto recursively generate a flat Map of the VNode tree.- Refactored
src/mixin/VdomLifecycle.mjs -> syncVnodeTree()to use this new map andSetlookups.- Created
test/playwright/unit/app/devindex/GridScrollProfile.spec.mjsto establish an empirical benchmark using the 50k dataset and ~40 rows with 30 columns.- Measured a drop in synchronous
syncVnodeTreeoverhead during scrolling deltas from ~167ms down to ~45ms (~73% improvement).The N² trap has been bypassed, freeing up massive App Worker CPU cycles during rapid grid scrolling.
tobiu closed this issue on Mar 8, 2026, 9:27 PM
tobiu cross-referenced by #9399 on Mar 8, 2026, 9:41 PM
Problem: During a performance inspection of the App Worker while rapidly scrolling a Grid, it was discovered that
Neo.util.VNode.find()is responsible for ~50% of the total App Worker CPU time and ~37% of the self-time (amounting to seconds of lock-up).This is caused by an
O(N²)performance trap insidesrc/mixin/VdomLifecycle.mjs -> syncVnodeTree(). Currently, the method iterates over allchildComponents(which can be hundreds of nodes in a grid) and callsVNodeUtil.find(me.vnode, component.vdom.id)for each one. This results in a full recursive tree traversal per child component on every update cycle.Furthermore, during the unmount check,
Array.includes()is used inside a loop against the same massivechildComponentsarray, creating a secondary bottleneck.Proposed Solution: Instead of repeatedly traversing the tree, we should do a single pass to create a flat map of the VNode structure, achieving
O(1)lookups.Neo.util.VNode.createMap(vnode)(inspired byNeo.vdom.Helper.createVnodeMap).syncVnodeTreeto generate this map once at the start of the method and use it forO(1)node resolution.childComponentsto aSetor use a different strategy to eliminate theO(N²)overhead of!childComponents.includes(component).Testing & Verification Strategy: To measure the impact, we will create a new Playwright unit test (
test/playwright/unit/app/devindex/GridScrollProfile.spec.mjs) that utilizes the DevIndexGridContainerand the 50k users dataset. By simulating a vertical scroll (triggering a massive VDOM delta on the child components) and measuring the execution time before and after the refactoring, we can explicitly quantify the performance gain.