Skip to content

Selector: Optimize jQuery.uniqueSort duplicate removal#5830

Merged
mgol merged 2 commits into
jquery:mainfrom
metsw24-max:uniqueSort-two-pointer-dedup
May 11, 2026
Merged

Selector: Optimize jQuery.uniqueSort duplicate removal#5830
mgol merged 2 commits into
jquery:mainfrom
metsw24-max:uniqueSort-two-pointer-dedup

Conversation

@metsw24-max
Copy link
Copy Markdown
Contributor

Summary

This PR refactors the duplicate removal logic in jQuery.uniqueSort to improve performance and reduce memory overhead.

The previous implementation relied on repeated splice operations inside a loop and used an auxiliary duplicates array, resulting in O(N²) time complexity and O(N) additional space in the worst case.

This patch replaces that logic with a two-pointer array compaction approach that performs deduplication in a single pass and trims the array with a single splice call.

Key improvements:

  • Reduces time complexity of duplicate removal from O(N²) to O(N)
  • Eliminates auxiliary array usage (O(N) → O(1) space)
  • Preserves existing behavior and output ordering
  • Keeps in-place mutation semantics unchanged

Copy link
Copy Markdown
Member

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense intuitively, and I also confirmed that this is significantly faster in practice after refactoring the new implementation for an apples-to-apples comparison regarding how to loop: https://jsfiddle.net/fnr2jLgz/

I'd like to accept this, but we should iterate on optimizing its post-gzip size.

Comment thread src/selector/uniqueSort.js Outdated
Comment on lines 73 to 74
var j = 0,
i = 0;
Copy link
Copy Markdown
Member

@gibson042 gibson042 May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can both just start at one, eliminating the initial reassignment.

Suggested change
var j = 0,
i = 0;
var j = 1,
i = 1;

Comment thread src/selector/uniqueSort.js Outdated
Comment on lines +81 to +82
j = 1;
for ( i = 1; i < results.length; i++ ) {
Copy link
Copy Markdown
Member

@gibson042 gibson042 May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
j = 1;
for ( i = 1; i < results.length; i++ ) {
for ( ; i < results.length; i++ ) {

This deviates from most of our loops, which have the form they do because of a historical need to work with NodeLists in which the numeric length might be shadowed by a constituent element with id "length". But performance-wise, it's much preferable to avoid walking off the end of a collection, especially when it is an array (#3769), and I'd like to migrate to this pattern (and I think we're safe here even without broad migration).

And all that aside, this should be preceded by an explanatory comment, e.g.

Suggested change
j = 1;
for ( i = 1; i < results.length; i++ ) {
// Pack the first instance of each unique element into the start of
// results (starting at index 1 because index 0 is always kept), then
// splice away the tail of duplicates.
for ( ; i < results.length; i++ ) {

Comment thread src/selector/uniqueSort.js Outdated
while ( j-- ) {
splice.call( results, duplicates[ j ], 1 );
}
splice.call( results, j, results.length - j );
Copy link
Copy Markdown
Member

@gibson042 gibson042 May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteCount can be omitted to delete all elements from the specified index.

Suggested change
splice.call( results, j, results.length - j );
splice.call( results, j );

And we can also get a substantial speedup of this final step when dealing with an actual array rather than a jQuery instance (about 3x in V8/Chrome and 1.5x in JavaScriptCore/Safari):

Suggested change
splice.call( results, j, results.length - j );
if (Array.isArray(results)) {
results.length = j;
} else {
splice.call( results, j );
}

@timmywil timmywil added this to the 4.1.0 milestone May 4, 2026
@metsw24-max
Copy link
Copy Markdown
Contributor Author

@gibson042 i updated as per u suggested

Copy link
Copy Markdown
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@mgol mgol merged commit 596c962 into jquery:main May 11, 2026
16 checks passed
@mgol mgol added the Selector label May 11, 2026
@mgol mgol changed the title Optimize jQuery.uniqueSort duplicate removal Selector: Optimize jQuery.uniqueSort duplicate removal May 11, 2026
mgol pushed a commit that referenced this pull request May 11, 2026
Refactor the duplicate removal logic in `jQuery.uniqueSort` to improve
performance and reduce memory overhead.

The previous implementation relied on repeated splice operations inside
a loop and used an auxiliary duplicates array, resulting in O(N²) time
complexity and O(N) additional space in the worst case.

This patch replaces that logic with a two-pointer array compaction approach
that performs deduplication in a single pass and trims the array with a single
splice call.

Key improvements:
- Reduces time complexity of duplicate removal from O(N²) to O(N)
- Eliminates auxiliary array usage (O(N) → O(1) space)
- Preserves existing behavior and output ordering
- Keeps in-place mutation semantics unchanged

Closes gh-5830
@mgol
Copy link
Copy Markdown
Member

mgol commented May 11, 2026

Landed on main in 35789e7. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

4 participants