Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid reading outside of collection bounds #407

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

mathiasbynens
Copy link
Contributor

@mathiasbynens mathiasbynens commented Aug 29, 2017

Consider the following collection:

const array = ['a', 'b', 'c'];

Retrieving array[0] can be done relatively quickly. However, when the property doesn’t exist on the receiver, JavaScript engines must continue to look up the prototype chain until either the property is found or the chain ends. This is inherently slower than not doing any prototype chain lookups. Retrieving an out-of-bounds index, e.g. array[3], triggers this scenario, resulting in decreased performance.

This patch changes the way some loops are written to avoid running into the slow case unnecessarily.


Similar patch for jQuery: jquery/jquery#3769

@jsf-clabot
Copy link

jsf-clabot commented Aug 29, 2017

CLA assistant check
All committers have signed the CLA.

@markelog
Copy link
Member

Hey.

This looks nice to me. How this reflects on the byte size though? Maybe you could provide a small jsperf as well?

@@ -671,8 +671,10 @@ setDocument = Sizzle.setDocument = function( node ) {

// Fall back on getElementsByName
elems = context.getElementsByName( id );
length = elems.length;
i = 0;
Copy link
Member

Choose a reason for hiding this comment

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

This could moved to the body of a for right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, for both i and length. However, i was already outside of the for, so I decided to follow the existing style.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, that because of the https://contribute.jquery.org/style-guide/js/#good-examples, i.e. it should be fine to put i to the body, as long its initialised on top of the scope, like we doing here – https://github.com/jquery/jquery/blob/692f9d4db30c9c6c4f6bc76005cf153586202fa6/src/effects.js#L614 :)

@@ -671,8 +671,10 @@ setDocument = Sizzle.setDocument = function( node ) {

// Fall back on getElementsByName
elems = context.getElementsByName( id );
length = elems.length;
Copy link
Member

@gibson042 gibson042 Aug 30, 2017

Choose a reason for hiding this comment

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

NodeList#length can be an element in IE<9 (and we should have a test where that is the case on this line). cf. f6e2fc5#diff-7d34356c0b7229dd5da8b6c7711b32b7R1733

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, something like this?

if (typeof elem.length === 'number') {
  fastPath();
} else {
  slowPathForLegacyBrowsers();
}

Copy link
Member

Choose a reason for hiding this comment

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

Yes, provided we can get it without excessive file size increase.

Copy link
Member

Choose a reason for hiding this comment

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

example:

for ( ; i !== len && (elem = elems[i]) != null; i++ ) {

@@ -702,11 +704,13 @@ setDocument = Sizzle.setDocument = function( node ) {
tmp = [],
i = 0,
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
results = context.getElementsByTagName( tag );
results = context.getElementsByTagName( tag ),
length = results.length;
Copy link
Member

Choose a reason for hiding this comment

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

Same problem here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -1054,17 +1058,19 @@ Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
j = 0,
i = 0;
i = 0,
length = results.length;
Copy link
Member

Choose a reason for hiding this comment

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

Same problem here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Consider the following collection:

    const array = ['a', 'b', 'c'];

Retrieving `array[0]` can be done relatively quickly. However, when the property
doesn’t exist on the receiver, JavaScript engines must continue to look up the
prototype chain until either the property is found or the chain ends. This is
inherently slower than *not* doing any prototype chain lookups. Retrieving an
out-of-bounds index, e.g. `array[3]`, triggers this scenario, resulting in
decreased performance.

This patch changes the way some loops are written to avoid running into the slow
case unnecessarily.
@gibson042
Copy link
Member

This is pretty big... can you put together a jsPerf or other benchmark (example, and yes I'm aware of the irony) showing the value?

   raw     gz Sizes
 65502  19586 dist/sizzle.js
 19851   7421 dist/sizzle.min.js

   raw     gz Compared to master @ 840b647cd1ddd9d9aec3cde73af3f697fbf068d7
  +194    +51 dist/sizzle.js
   +82    +54 dist/sizzle.min.js

i = 0;
while ( (elem = elems[i++]) ) {
for ( ; i !== length && (elem = elems[ i ]) != null; i++ ) {
Copy link
Member

Choose a reason for hiding this comment

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

This is probably not an issue, but I feel like i < length is safer.

Copy link
Member

Choose a reason for hiding this comment

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

i < length would definitely be an issue, because length might be a DOM node in IE<9.

Copy link
Member

Choose a reason for hiding this comment

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

makes sense.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we could put in a comment relating that as a reminder.

@dmethvin
Copy link
Member

dmethvin commented Nov 4, 2017

If the Chrome perf quirk was the main driver for this, that has been fixed.

Base automatically changed from master to main February 16, 2021 13:15
@mgol mgol closed this Jan 10, 2022
@mgol mgol reopened this Jan 10, 2022
@linux-foundation-easycla
Copy link

CLA Not Signed

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

Successfully merging this pull request may close these issues.

None yet

7 participants