LearnNewsExamplesServices
Frontmatter
id9333
titleCollection: Replace array spreads with native slice to fix GC stalls on massive datasets
stateClosed
labels
bugaiperformance
assigneestobiu
createdAtFeb 27, 2026, 12:56 PM
updatedAtFeb 27, 2026, 12:58 PM
githubUrlhttps://github.com/neomjs/neo/issues/9333
authortobiu
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtFeb 27, 2026, 12:58 PM

Collection: Replace array spreads with native slice to fix GC stalls on massive datasets

Closed v12.0.0 bugaiperformance
tobiu
tobiu commented on Feb 27, 2026, 12:56 PM

During "Turbo Mode" operations with massive datasets (e.g., 50k+ items), Neo.collection.Base utilizes the spread operator ([...array]) in several critical hot paths (initialization, filtering, sorting).

When a 50,000 item array is spread, it unpacks all references onto the JavaScript engine's call stack. This can exceed browser stack limits (causing crashes) and generates massive, instantaneous memory allocation spikes that force the Garbage Collector to stall the Main/App Worker thread.

In our DevIndex profiling, the first filter operation triggered three separate 50k spread operations in rapid succession (afterSetSourceId, doSort, and filter), causing a 3-second UI freeze.

Fix: Replace all instances of [...largeArray] with largeArray.slice() within src/collection/Base.mjs. Array.prototype.slice() is a native C++ engine method that clones arrays directly in heap memory, completely bypassing the JS call stack and executing ~5x faster with significantly less GC thrashing.

tobiu added the bug label on Feb 27, 2026, 12:56 PM
tobiu added the ai label on Feb 27, 2026, 12:56 PM
tobiu added the performance label on Feb 27, 2026, 12:56 PM
tobiu referenced in commit f86087e - "perf(Collection): Replace array spreads with native slice to fix GC stalls on massive datasets (#9333)" on Feb 27, 2026, 12:57 PM
tobiu assigned to @tobiu on Feb 27, 2026, 12:57 PM
tobiu
tobiu Feb 27, 2026, 12:57 PM

Input from Gemini 3.1 Pro:

✦ The "Spread Operator Purge" is complete.

I have replaced all massive array spreads in src/collection/Base.mjs with native .slice() calls:

  • afterSetSourceId (preventing 50k spread on allItems creation)
  • clone()
  • doSort() (preventing 50k spread for previousItems)
  • filter() (preventing multiple 50k spreads for backups)

This definitively eliminates the huge call stack and GC memory thrashing that was occurring during the initial first-time filter in Turbo Mode. Changes are merged into dev. Closing ticket!

tobiu closed this issue on Feb 27, 2026, 12:58 PM