-
Notifications
You must be signed in to change notification settings - Fork 30k
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
child_process: fix deoptimizing use of arguments #11535
child_process: fix deoptimizing use of arguments #11535
Conversation
@@ -195,6 +195,8 @@ exports.execFile = function(file /*, args, options, callback*/) { | |||
|
|||
var ex = null; | |||
|
|||
var cmd = file; | |||
|
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 is this change 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.
It prevents named parameter from leaking in a lower scope causing deopt. See https://bugs.chromium.org/p/v8/issues/detail?id=6010
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've just upped this line from the inner exithandler()
function. It seems this does not cause any scope conflict.
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.
Eh… Understood.
Perhaps this deserves some kind of comment there, /cc @mscdex.
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.
Also /cc @nodejs/v8 and @targos.
I don't think that this is the only place that is affected by our update to V8 5.6, I expect more deopts like this.
This better should be fixed on the V8 side instead of rewriting all the places in Node.js where a named function parameter is used in a child scope.
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.
It happens only if arguments[i]
is read, so it could not be a huge number of cases.
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 the whole arguments[..]
and named parameter mixed usage deopt has been around for awhile, long before V8 5.6?
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.
@mscdex These two factors do not cause deopt in:
v8 4.5.103.45 (Node.js 4.8.0 x64)
v8 5.1.281.93 (Node.js 6.10.0 x64)
v8 5.5.372.40 (Node.js 7.6.0 x64)
And I have not seen this case in any articles on v8 optimization.
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 the whole arguments[..] and named parameter mixed usage deopt has been around for awhile, long before V8 5.6?
Only outside of the strict mode if I remember things correctly, but I could be mistaken.
The linked bug is about the code in the strict mode.
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.
@ChALkeR There was a slightly different case in the sloppy mode: reassigning a defined parameter + mentioning arguments
.
This seems like the wrong approach to me. It introduces more uses of |
@cjihrig I was not sure I should refactor it, I've just tried not to be very intrusive. But yes, I can try to add named parameters. |
@cjihrig Fixed for |
Build and tests are OK. I will launch a new benchmark run and will post the results a bit later. |
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 looks better to me. One last comment though. Is there a performance hit for reassigning named parameters. It looks like we might be able to get away with not having separate variables from the parameters. I know we do that in other places in core (and it looks cleaner), but again, I'm not sure about any performance hit.
lib/child_process.js
Outdated
let options; | ||
let callback; | ||
|
||
if (typeof arguments[1] === 'function') { | ||
if (options_ && typeof options_ === 'function') { |
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.
You shouldn't need the leading options_
now, right?
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've though it would eliminate unneeded type check / function call (isArray()
below) on undefined
and would be a bit faster. Should I delete them?
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.
It's hard to know what V8 will do from version to version, but won't the leading options_
require a conversion to a boolean and comparison against true
?
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.
It seems so) So should I delete them?
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.
Seems fine to me.
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.
Fixed.
lib/child_process.js
Outdated
options = arguments[2]; | ||
} else if (arguments[1] !== undefined && | ||
(arguments[1] === null || typeof arguments[1] !== 'object')) { | ||
if (args_ && Array.isArray(args_)) { |
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.
Same comment here about the leading args_
@cjihrig If I remember right, in the ES6 named parameters and Maybe this simplification could be done as a separate PR by a more experienced contributor? It seems there are many other similar cases in the libs. |
I don't think that matters. That's more of a style preference. Since we already do it in core, I think it only matters if it hurts performance.
I'd rather get this figured out in one PR than having a second PR to change the exact same thing (churn, backporting overhead, etc.). |
So should I wait till some more opinions or should I remove the mediatorial params now? |
@mscdex probably knows the answer :-) |
|
||
const method = cp[methodName]; | ||
|
||
if (methodName === 'exec') { |
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 would be more manageable as a switch (methodName) {}
structure... with each case handled in a separate function.
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 will try.
const method = cp[methodName]; | ||
|
||
if (methodName === 'exec') { | ||
if (params === 1) { |
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 would also be better as a switch (params) {}
block
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.
Hopefully fixed.
New benchmark with amended commits:
|
@vsemozhetbyt would you be OK with using your benchmark to investigate the idea from #11535 (review). Then, we can get this through the CI and landed. |
@cjihrig It seems OK:
|
Awesome. The code looks cleaner, and has less use of |
Fixed functions: normalizeExecArgs(), exports.execFile(), normalizeSpawnArguments() Refs: #10323 Refs: https://bugs.chromium.org/p/v8/issues/detail?id=6010
Squashed. |
Landed in dd81d53. Thanks for working through this. |
Removed or fixed use of arguments in execFile(), normalizeExecArgs(), and normalizeSpawnArguments(). Refs: #10323 Refs: https://bugs.chromium.org/p/v8/issues/detail?id=6010 PR-URL: #11535 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
This is not landing cleanly on v7.x-staging. Mind backporting? |
@evanlucas I never backported a PR. I shall try to learn how to do this and then shall try to apply if nobody does it sooner. |
@evanlucas PTAL: #11748 |
Removed or fixed use of arguments in execFile(), normalizeExecArgs(), and normalizeSpawnArguments(). Refs: #10323 Refs: https://bugs.chromium.org/p/v8/issues/detail?id=6010 Backport-Of: #11535 PR-URL: #11748 Reviewed-By: James M Snell <jasnell@gmail.com>
See #11748 (comment) Should this be backported to v6.x-staging? If yes please follow the guide and raise a backport PR, if no let me know or add the |
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
child_process, benchmarks
This is the last unaddressed case from mentioned in the #10323. PR fixes three functions.
1. Function
normalizeExecArgs()
is called byexports.exec()
andexports.execSync()
with a variable number of parameters.To check deoptimizations, run this code with
--trace_opt --trace_deopt
flags:You will see messages about
normalizeExecArgs()
deoptimization.None of the current benchmarks checks this case: the only benchmark calling
exports.exec()
does it once and always with 2 parameters.2. Function
exports.execFile()
is called by users with a variable number of parameters. See also #10323 (comment)To check deoptimizations, run this code with
--trace_opt --trace_deopt
flags:You will see messages about
exports.execFile()
deoptimization.None of the current benchmarks checks
exports.execFile()
.3. Function
normalizeSpawnArguments()
is called byexports.spawn()
,exports.spawnSync()
, andexports.execFileSync()
with a variable number of parameters.To check deoptimizations, run this code with
--trace_opt --trace_deopt
flags:You will see messages about
normalizeSpawnArguments()
deoptimization.None of the current benchmarks checks
exports.spawnSync()
orexports.execFileSync()
. Two of them callexports.spawn()
once with 3 params (not appropriate). One of them callsexports.spawn()
1000 times with 2 params, but it does not cover all sub-cases.4. While fixing these cases, I've found an absolutely new deopt case in the
exports.execFile()
function introduced by v8 5.6. See the fourth commit and explanation in the upstream issue.5. As changes affect almost all main
child_process
methods (except.fork()
), I've added a benchmark calling these 6 methods with 1, 2, 3, and 4 params (when it is appropriate). Benchmarking is a bit murky in this case because of many external sync/async processes involved, but it seems no significant degradation is caused, moreover some tiny improvement is evident.