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

url: avoid instanceof for WHATWG URL #11690

Closed
wants to merge 1 commit into from

Conversation

mscdex
Copy link
Contributor

@mscdex mscdex commented Mar 5, 2017

This PR replaces instanceof checks with symbol checks. Various relevant results:

                                                                             improvement confidence      p.value
 url/url-searchparams-read.js n=50000000 param="nonexistent" method="get"       195.08 %        *** 5.548226e-41
 url/url-searchparams-read.js n=50000000 param="nonexistent" method="getAll"    174.48 %        *** 3.225875e-29
 url/url-searchparams-read.js n=50000000 param="nonexistent" method="has"       190.68 %        *** 9.000669e-32
 url/url-searchparams-read.js n=50000000 param="one" method="get"               269.63 %        *** 2.180791e-33
 url/url-searchparams-read.js n=50000000 param="one" method="getAll"            111.73 %        *** 1.724534e-25
 url/url-searchparams-read.js n=50000000 param="one" method="has"               266.25 %        *** 5.498456e-33
 url/url-searchparams-read.js n=50000000 param="three" method="get"             203.56 %        *** 2.754902e-45
 url/url-searchparams-read.js n=50000000 param="three" method="getAll"          108.95 %        *** 1.790912e-35
 url/url-searchparams-read.js n=50000000 param="three" method="has"             198.65 %        *** 1.896093e-32
 url/url-searchparams-read.js n=50000000 param="two" method="get"               248.88 %        *** 4.737990e-41
 url/url-searchparams-read.js n=50000000 param="two" method="getAll"            112.93 %        *** 2.182157e-31
 url/url-searchparams-read.js n=50000000 param="two" method="has"               224.41 %        *** 2.056153e-30
 url/whatwg-url-properties.js n=10000000 prop="origin" input="auth"              12.59 %         ** 0.0035532598
 url/whatwg-url-properties.js n=10000000 prop="origin" input="dot"               10.53 %         ** 0.0041671062
 url/whatwg-url-properties.js n=10000000 prop="origin" input="file"              72.86 %        *** 0.0004049447
 url/whatwg-url-properties.js n=10000000 prop="origin" input="idn"                5.76 %            0.1494657794
 url/whatwg-url-properties.js n=10000000 prop="origin" input="javascript"        54.73 %         ** 0.0018129659
 url/whatwg-url-properties.js n=10000000 prop="origin" input="long"              10.20 %          * 0.0429791847
 url/whatwg-url-properties.js n=10000000 prop="origin" input="percent"            1.84 %            0.3920201593
 url/whatwg-url-properties.js n=10000000 prop="origin" input="short"             11.54 %        *** 0.0001064664
 url/whatwg-url-properties.js n=10000000 prop="origin" input="ws"                10.64 %         ** 0.0074980105

CI: https://ci.nodejs.org/job/node-test-pull-request/6707/

/cc @nodejs/url

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)
  • url-whatwg

@mscdex mscdex added performance Issues and PRs related to the performance of Node.js. whatwg-url Issues and PRs related to the WHATWG URL implementation. labels Mar 5, 2017
@nodejs-github-bot nodejs-github-bot added dont-land-on-v4.x whatwg-url Issues and PRs related to the WHATWG URL implementation. labels Mar 5, 2017
Copy link
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

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

I vaguely remember some of the slowdowns of instanceof are already fixed in the v8 lkgr fork(5.8-ish)...at least for Buffer last time I played around with it. Nonethess I think testing for internal symbols is better anyway.

Copy link
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

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

Hit the wrong option..

@@ -701,7 +701,7 @@ class URLSearchParams {
}

[util.inspect.custom](recurseTimes, ctx) {
if (!this || !(this instanceof URLSearchParams)) {
if (!this || this[searchParams] === undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

The searchParams symbol is used by the URL class as well, so this will no longer error out on URL objects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've now added per-class symbols that are checked instead.

@mscdex
Copy link
Contributor Author

mscdex commented Mar 6, 2017

CI again after symbol check changes: https://ci.nodejs.org/job/node-test-pull-request/6718/

@@ -7,6 +7,8 @@ const context = Symbol('context');
const cannotBeBase = Symbol('cannot-be-base');
const special = Symbol('special');
const searchParams = Symbol('query');
const URLInstance = Symbol('url-instance');
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer urlInstance just to be consistent with searchParamsInstance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

@@ -701,7 +705,7 @@ class URLSearchParams {
}

[util.inspect.custom](recurseTimes, ctx) {
if (!this || !(this instanceof URLSearchParams)) {
if (!this || this[searchParamsInstance] !== true) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm still reluctant about writing out this[searchParamsInstance] !== true when a !this[searchParamsInstance] would do. Is this change visible performance-wise?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just being more explicit about the expected value.

Copy link
Member

Choose a reason for hiding this comment

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

+1 to be more explicit.

@TimothyGu TimothyGu dismissed their stale review March 6, 2017 02:52

Review of an older iteration of the PR

@@ -7,6 +7,8 @@ const context = Symbol('context');
const cannotBeBase = Symbol('cannot-be-base');
const special = Symbol('special');
const searchParams = Symbol('query');
const urlInstance = Symbol('url-instance');
const searchParamsInstance = Symbol('searchParams-instance');
Copy link
Member

Choose a reason for hiding this comment

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

Why not use the existing context and searchParams symbols rather than introducing new ones? All URL objects should have the context symbol set. Likewise all URLSearchParams instances should have the searchParams symbol set (I believe)

Copy link
Contributor Author

@mscdex mscdex Mar 7, 2017

Choose a reason for hiding this comment

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

As @TimothyGu noted, both classes use both of those symbols, so they don't currently each have a unique symbol?

Copy link
Member

Choose a reason for hiding this comment

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

You can probably separate them like this:

if (url[searchParams]) {
  if (url[searchParams][searchParams]) { }  // URL, it's an array
  else {}  // URLSearchParams, it's undefined
}

Copy link
Member

@TimothyGu TimothyGu left a comment

Choose a reason for hiding this comment

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

Any updates on this?

@@ -219,7 +221,8 @@ class URL {
constructor(input, base) {
// toUSVString is not needed.
input = '' + input;
if (base !== undefined && !(base instanceof URL))
this[urlInstance] = true;
Copy link
Member

Choose a reason for hiding this comment

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

I might put the urlInstance in the prototype to avoid having to set this every single time an object is created.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then URL.prototype will pass the brand check, which seems bad.

@@ -646,6 +649,7 @@ class URLSearchParams {
// Default parameter is necessary to keep URLSearchParams.length === 0 in
// accordance with Web IDL spec.
constructor(init = undefined) {
this[searchParamsInstance] = true;
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

@mscdex
Copy link
Contributor Author

mscdex commented Mar 29, 2017

Alright, I've switched to checking using only searchParams. New results with V8 5.7:

                                                                              improvement confidence      p.value
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="get"       163.51 %        *** 2.269241e-61
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="getAll"    160.05 %        *** 5.142371e-51
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="has"       167.00 %        *** 1.355736e-46
 url/url-searchparams-read.js n=20000000 param="one" method="get"               236.25 %        *** 8.197452e-52
 url/url-searchparams-read.js n=20000000 param="one" method="getAll"            120.78 %        *** 2.622135e-51
 url/url-searchparams-read.js n=20000000 param="one" method="has"               243.64 %        *** 6.676611e-50
 url/url-searchparams-read.js n=20000000 param="three" method="get"             194.93 %        *** 5.614993e-59
 url/url-searchparams-read.js n=20000000 param="three" method="getAll"          106.35 %        *** 3.023894e-81
 url/url-searchparams-read.js n=20000000 param="three" method="has"             189.66 %        *** 7.362414e-46
 url/url-searchparams-read.js n=20000000 param="two" method="get"               218.28 %        *** 1.350097e-67
 url/url-searchparams-read.js n=20000000 param="two" method="getAll"            113.33 %        *** 2.637819e-83
 url/url-searchparams-read.js n=20000000 param="two" method="has"               218.26 %        *** 4.443562e-86
 url/whatwg-url-properties.js n=300000 prop="origin" input="auth"                12.40 %        *** 3.767663e-16
 url/whatwg-url-properties.js n=300000 prop="origin" input="dot"                  7.74 %        *** 8.915982e-09
 url/whatwg-url-properties.js n=300000 prop="origin" input="file"                77.36 %        *** 1.300946e-26
 url/whatwg-url-properties.js n=300000 prop="origin" input="idn"                  4.96 %        *** 2.077301e-10
 url/whatwg-url-properties.js n=300000 prop="origin" input="javascript"          83.97 %        *** 6.470616e-48
 url/whatwg-url-properties.js n=300000 prop="origin" input="long"                 9.44 %        *** 3.128818e-11
 url/whatwg-url-properties.js n=300000 prop="origin" input="percent"              7.26 %        *** 3.400718e-14
 url/whatwg-url-properties.js n=300000 prop="origin" input="short"                9.22 %        *** 1.478553e-11
 url/whatwg-url-properties.js n=300000 prop="origin" input="ws"                   7.70 %        *** 9.628009e-09

Results are pretty close to what they were before.

Copy link
Member

@jasnell jasnell left a comment

Choose a reason for hiding this comment

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

Generally LGTM with a question/suggestion

@@ -239,8 +239,10 @@ class URL {
constructor(input, base) {
// toUSVString is not needed.
input = `${input}`;
if (base !== undefined && !(base instanceof URL))
if (base !== undefined &&
(!base[searchParams] || !base[searchParams][searchParams])) {
Copy link
Member

Choose a reason for hiding this comment

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

@mscdex ... is there benefit to moving this into a separate isURL(u) function that can be exported by internal/url?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Performance-wise? No. There's actually a small but measurable performance hit when extracting all of the logic to a separate function, compared to having it inline. For example:

                                                                              improvement confidence      p.value
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="get"       165.07 %        *** 1.189398e-66
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="getAll"    139.46 %        *** 4.331942e-46
 url/url-searchparams-read.js n=20000000 param="nonexistent" method="has"       161.48 %        *** 2.741360e-52
 url/url-searchparams-read.js n=20000000 param="one" method="get"               222.70 %        *** 3.111318e-46
 url/url-searchparams-read.js n=20000000 param="one" method="getAll"            113.31 %        *** 1.122968e-53
 url/url-searchparams-read.js n=20000000 param="one" method="has"               232.04 %        *** 1.571222e-55
 url/url-searchparams-read.js n=20000000 param="three" method="get"             184.13 %        *** 2.078669e-48
 url/url-searchparams-read.js n=20000000 param="three" method="getAll"          100.34 %        *** 4.591714e-56
 url/url-searchparams-read.js n=20000000 param="three" method="has"             181.29 %        *** 7.340053e-43
 url/url-searchparams-read.js n=20000000 param="two" method="get"               209.77 %        *** 4.581349e-54
 url/url-searchparams-read.js n=20000000 param="two" method="getAll"            102.89 %        *** 2.001755e-36
 url/url-searchparams-read.js n=20000000 param="two" method="has"               203.09 %        *** 1.810527e-44

Copy link
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

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

Still LGTM

@joyeecheung
Copy link
Member

@jasnell
Copy link
Member

jasnell commented Apr 4, 2017

@TimothyGu
Copy link
Member

Landed in b76a350.

@TimothyGu TimothyGu closed this Apr 5, 2017
TimothyGu pushed a commit that referenced this pull request Apr 5, 2017
PR-URL: #11690
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com>
@mscdex mscdex deleted the url-whatwg-perf branch April 5, 2017 04:09
@italoacasas
Copy link
Contributor

cc @mscdex

@guybedford
Copy link
Contributor

This has come up again in #22506. @mscdex do you think we could move to an instanceof check at this point from a performance perspective yet? Would value your feedback on this.

@mscdex
Copy link
Contributor Author

mscdex commented Aug 28, 2018

@guybedford I do not know offhand. Someone would have to be sure to run benchmarks on both implementations and compare the results for master.

@devsnek
Copy link
Member

devsnek commented Aug 28, 2018

I think there's also a concern about Object.create(URL.prototype) matching instanceof. If we use instanceof and then try to access our symbol properties it will fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Issues and PRs related to the performance of Node.js. whatwg-url Issues and PRs related to the WHATWG URL implementation.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants