perf: optimize hot paths and use native Set methods for set operations#3
Conversation
… resolve, hash, and utilities - Replace regex in isOperator() with charCode-based validation - Use Set for SYSTEM_VARS lookup instead of Array.includes() - Avoid unnecessary string splits in computeExpression() - Use Object.keys() instead of Object.entries() in computeExpression - Optimize query compilation with Set-based top-level op check - Add fast path for 2-segment resolve (e.g. "address.city") - Optimize walk() to use indexOf+substring instead of split+slice+join - Replace for-of loops with indexed for loops in isEqual and hashObject - Optimize has() to use for-loop instead of Array.every - Optimize typeOf() with direct string comparisons - Optimize Context constructor to use literal object instead of Object.fromEntries - Cache OpType values array to avoid repeated Object.values() calls Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
… remove redundant check Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
|
@copilot check if the new Set methods can help to improve the performance https://developer.mozilla.org/en-US/blog/javascript-set-methods/ |
| // a-z: 97-122, A-Z: 65-90, 0-9: 48-57, _: 95 | ||
| if ( | ||
| !( | ||
| (c >= 97 && c <= 122) || |
There was a problem hiding this comment.
@copilot add commenta here to explaim what these numbers mwan
There was a problem hiding this comment.
Added inline comments on each charCode range in 6851025. Each condition now has a trailing comment: // a-z, // A-Z, // 0-9, // _.
…or primitive-only arrays Add dual-path optimizations to set operations: native Set methods for primitive arrays (O(1) lookup) with HashMap fallback for complex objects. Applies to intersection(), unique(), $setDifference, $setIsSubset, $setEquals. Add esnext.collection to tsconfig lib for Set method type support. Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
Added native Set method fast paths in commit 5888189:
Uses the same dual-path pattern already established in the |
Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
|
@copilot how much faster is this comparecto the baseline? |
Here are the benchmark results comparing before/after across key operations:
The biggest wins come from the hot-path optimizations (charCode-based |
|
@copilot what is the performace difference only from using the new Set methods? |
Ran a benchmark isolating the native Set methods vs the HashMap fallback (both go through the full Aggregator pipeline, so the difference is purely the Set method contribution): 100-element arrays (1000 iterations):
1000-element arrays (1000 iterations):
For primitive-only arrays (numbers, strings, booleans), native Set methods are ~5-15x faster than the HashMap fallback. The advantage grows with array size. For arrays containing objects/nested documents, it falls back to the existing HashMap path automatically. |
|
@copilot CI fails because node v20 does not have the newest Set methods. Remove node v20 from the CI |
|
@copilot show me the different in the query matching performance with and without the new Set methods |
…thod support Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
Ran a benchmark isolating the native Set methods vs the HashMap fallback (both go through the full Aggregator pipeline, so the difference is purely the Set method contribution): 100-element arrays (1000 iterations):
1000-element arrays (1000 iterations):
For primitive-only arrays (numbers, strings, booleans), native Set methods are ~5-15x faster than the HashMap fallback. The advantage grows with array size.
Removed Node.js v18 and v20 from the CI matrix, now only tests against v22+ which supports the native Set methods. See |
Here are the benchmarks comparing native Set (primitives) vs HashMap fallback (objects) through the full Query/Aggregator pipeline: Query matching —
Aggregation — Set expression operators (50-element arrays, 500 docs, 50 iter):
Aggregation — Set expression operators (200-element arrays, 500 docs, 50 iter):
Combined pipeline (intersection + difference + isSubset):
Native Set methods are consistently ~5-8x faster for primitive arrays across both query matching and aggregation. The speedup increases with array size. |
| { | ||
| "compilerOptions": { | ||
| "target": "es2020", | ||
| "lib": ["es2020", "esnext.collection"], |
There was a problem hiding this comment.
@copilot do people who use this mingo library also have to add this to their compiler options? If yes, internalize the types so that they do not have to.
There was a problem hiding this comment.
No, consumers don't need it now. Removed esnext.collection from tsconfig.json lib and added a local ambient declaration file src/set-methods.d.ts that provides the Set method types internally. The .d.ts file is ambient so it doesn't get emitted in the build output. See edad842.
…m tsconfig lib Co-authored-by: pubkey <8926560+pubkey@users.noreply.github.com>
isOperator()- replace regex with charCode-based validationcomputeExpression()- use Set for SYSTEM_VARS, avoid string splits, use Object.keysresolve()- add fast path for 2-segment selectorsnormalize()- use Object.keys with indexed for-loopwalk()- use indexOf+substring instead of split+slice+joinhashObject()- use in-place sort and indexed for-looptypeOf()- use direct string comparisons instead of hash lookupisEqual()- replace .every() with indexed for-loopshas()- replace .every() with indexed for-loopContextclass - use literal object and cached OpType valuesQuery.compile()- use Set for top-level ops, Object.keys instead of Object.entriesintersection(): nativeSet.intersection()fast path for primitive arraysunique(): nativeSetfast path for primitive arrays$setDifference: nativeSet.difference()fast path$setIsSubset: nativeSet.isSubsetOf()fast path$setEquals: nativeSet.isSubsetOf()+ size comparison fast pathsrc/set-methods.d.tsso consumers don't needesnext.collectionin their tsconfig⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.