-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
More URLSearchParams improvements #10399
Conversation
We could also consider exporting URLSearchParams from the querystring module. |
dd8fecb
to
06ba384
Compare
} | ||
} | ||
return values; | ||
} | ||
|
||
function getObjectFromParams(array) { | ||
const obj = Object.create(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't benched Object.create(null)
with V8 5.4 to see if it performs any better now, but you might want to consider using the same trick used in querystring.js for better performance.
I'm concerned about the performance hits here. Is an array absolutely necessary? |
@mscdex, first off this API is still considered experimental. Performance is not a first priority. Second, the benchmarks that really matter (iteration and reading) all show significant performance increase. Why I said "really matter" is because the plan is to eventually stop using querystring module, and because of that the use of conversion functions like var params = new URLSearchParams();
params.append('a', 'a');
params.append('b', 'b');
params.append('a', 'c');
for (var [ name, val ] of params) { console.log(name, val); } With object
With array (correct)
|
06ba384
to
22fb822
Compare
We will definitely need to put some work into the performance very soon, but I agree that the focus on standards compliance first is the right approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there!
const params = new URLSearchParams(str); | ||
const noop = () => {}; | ||
eval('%OptimizeFunctionOnNextCall(params.forEach)'); | ||
eval('%OptimizeFunctionOnNextCall(noop)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look at https://github.com/nodejs/node/blob/master/benchmark/common.js#L211 .. there is a utility method that takes care of the V8 optimization bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The v8ForceOptimization
function forces the this
context of the function to be null
, which means that functions that depend on this
to be set properly, like params.forEach
cannot be used with v8ForceOptimization
.
v8.setFlagsFromString('--allow_natives_syntax'); | ||
|
||
const bench = common.createBenchmark(main, { | ||
method: 'forEach iterator'.split(' '), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do it like this? why not ['forEach', 'iterator']
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was from the existing url/new-url-parse.js benchmark.
22fb822
to
009a147
Compare
@TimothyGu Can you rebase this onto the master? |
@joyeecheung, I have just done so. |
ddfc3bb
to
7df717c
Compare
@@ -0,0 +1,12 @@ | |||
'use strict'; | |||
exports.inputs = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file should be in benchmark/fixtures
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm did not notice that before. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The convention seems to be to include this type of data in all benchmark files, so I did that instead.
const URLSearchParams = require('url').URLSearchParams; | ||
const v8 = require('v8'); | ||
|
||
v8.setFlagsFromString('--allow_natives_syntax'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createBenchmark
now takes a third argument for flags that will be turned on when running main
(see the guide), so I think this can be replaced by passing a { flags: ['--allow_natives_syntax'] }
to createBenchmark
as the third argument.
function forEach(n) { | ||
const params = new URLSearchParams(str); | ||
const noop = () => {}; | ||
eval('%OptimizeFunctionOnNextCall(params.forEach)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make OptimizeFunctionOnNextCall
work properly, we should call it after making the first call to params.forEach
(it needs enough type info to optimize, which can be gathered in the first call).
BTW I am not sure we do need to do this step, because the optimizing compiler doesn't necessarily have enough type feedback to properly optimize this with just one call. I believe this conventions is to prevent the OSR(on-stack replacement) from affecting the result, but if the compiler doesn't have enough type info during the first optimization, it might still kick in again in the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I do have to look into these calls more carefully, esp. wrt. their necessity.
|
||
bench.start(); | ||
for (var i = 0; i < n; i += 1) | ||
params.forEach(noop); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing against a noop can easily lead to weird results when the optimizing compiler can do JIT optimizations. This line is a loop invariant so could be code-motioned, leaving us timing an empty loop(which could explain the 1862.07 % improvement, can't be sure without looking into the IR). Maybe we can do something with i and the params in the loop(thus breaking the invariant), and add another case with for-in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it is the fact that we now store an array so no further processing (conversion from object to array) is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When callback
is noop, it is possible for the optimizing compiler to eliminate callback.call(thisArg, value, key, this)
completely (again, can't be sure without looking into IR, but it's possible). Stopping reassigning pairs
definitely could contribute to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a noDead
variable similar to your benchmark PR. Also note the increased performance of iterators as well, as iterators do not have a callback.
+1 for better spec compliance. I am not very sure about the benchmark results without looking into IR but we can improve them later, no reason to block this(especially when this is blocking others like #10801 and #10635). Aside: the micro benchmarks under |
CI: https://ci.nodejs.org/job/node-test-pull-request/5866/ Anything else that need to be addressed other than the benchmark stuff? @jasnell |
Other than that I think this is good to go |
7df717c
to
5d67048
Compare
5d67048
to
87f6c3a
Compare
2900381
to
87f6c3a
Compare
@jasnell, any other things I need to change before this can be applied? |
@jasnell, ping. |
Landed 98bb65f |
- add some benchmarks for URLSearchParams - change URLSearchParams backing store to an array - add custom inspection for URLSearchParams and its iterators PR-URL: #10399 Reviewed-By: James M Snell <jasnell@gmail.com>
- add some benchmarks for URLSearchParams - change URLSearchParams backing store to an array - add custom inspection for URLSearchParams and its iterators PR-URL: nodejs#10399 Reviewed-By: James M Snell <jasnell@gmail.com>
- add some benchmarks for URLSearchParams - change URLSearchParams backing store to an array - add custom inspection for URLSearchParams and its iterators PR-URL: nodejs#10399 Reviewed-By: James M Snell <jasnell@gmail.com>
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
url
Description of change
Various improvements are done to the
URLSearchParams
class, including making the storage an array (in preparation for writing native C++ parsing and serialization routines, as well as to preserve the order of param entry addition), and adding support forutil.inspect
. Also, theURLSearchParams
constructor is now exported as a property of theurl
module.