LearnNewsExamplesServices
Frontmatter
id9398
titlePerformance: Eliminate O(N²) App Worker VDOM Traversal (syncVnodeTree)
stateClosed
labels
enhancementaiarchitectureperformance
assigneestobiu
createdAtMar 8, 2026, 8:57 PM
updatedAtMar 8, 2026, 9:27 PM
githubUrlhttps://github.com/neomjs/neo/issues/9398
authortobiu
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMar 8, 2026, 9:27 PM

Performance: Eliminate O(N²) App Worker VDOM Traversal (syncVnodeTree)

Closed v12.1.0 enhancementaiarchitectureperformance
tobiu
tobiu commented on Mar 8, 2026, 8:57 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 inside src/mixin/VdomLifecycle.mjs -> syncVnodeTree(). Currently, the method iterates over all childComponents (which can be hundreds of nodes in a grid) and calls VNodeUtil.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 massive childComponents array, 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.

  1. New Utility: Implement Neo.util.VNode.createMap(vnode) (inspired by Neo.vdom.Helper.createVnodeMap).
  2. Refactor: Update syncVnodeTree to generate this map once at the start of the method and use it for O(1) node resolution.
  3. Optimize Array Lookup: Convert childComponents to a Set or use a different strategy to eliminate the O(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 DevIndex GridContainer and 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.

tobiu added the enhancement label on Mar 8, 2026, 8:57 PM
tobiu added the ai label on Mar 8, 2026, 8:57 PM
tobiu added the architecture label on Mar 8, 2026, 8:57 PM
tobiu added the performance label on Mar 8, 2026, 8:57 PM
tobiu referenced in commit 9fcf3f9 - "perf: Eliminate O(N²) App Worker VDOM Traversal in syncVnodeTree (#9398)" on Mar 8, 2026, 9:26 PM
tobiu assigned to @tobiu on Mar 8, 2026, 9:26 PM
tobiu
tobiu Mar 8, 2026, 9:26 PM

Input from Gemini 3.1 Pro:

Task Completed:

  • Implemented Neo.util.VNode.createMap to recursively generate a flat Map of the VNode tree.
  • Refactored src/mixin/VdomLifecycle.mjs -> syncVnodeTree() to use this new map and Set lookups.
  • Created test/playwright/unit/app/devindex/GridScrollProfile.spec.mjs to establish an empirical benchmark using the 50k dataset and ~40 rows with 30 columns.
  • Measured a drop in synchronous syncVnodeTree overhead 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
tobiu referenced in commit 4b0908d - "fix: Ensure VNodeUtil.createMap mimics VNodeUtil.find strictly (#9398)" on Mar 8, 2026, 9:58 PM