Another case of possibly deoptimizing use of arguments
in libs
#11001
Labels
performance
Issues and PRs related to the performance of Node.js.
v8 engine
Issues and PRs related to the V8 dependency.
In this issue I've missed a case which is much more difficult to trace. That report has caused some fixes, so maybe this is also worth reporting.
However, some disclaimer is needed: I could get the theory or its application wrong here; I could miss or detect wrongly some cases; I am not sure if optimization is worth refactoring in each case. Moreover, spread is increasingly gaining speed now, so maybe upgrade to it is more worth efforts.
I. Definition
Leaking
arguments
is considered safe only withinfunc.apply(context, arguments)
. However, this call site must be monomorphic. See the last warning in this chapter.To be monomorphic,
func
must have the same hidden class. According to this comment, these functions have different hidden classes:bind()
)So, each
func.apply(context, arguments)
fragment should be checked forfunc
monomorphism.II. Example of deoptimization
This is the code to compare two cases with monomorphic and polymorphic call sites. In the comment below the code, you can see the time difference and deoptimization warning.
III. Types of cases
If I get it right, there are three group of cases here (from the most dangerous to the least dangerous):
func
is an argument gotten from a userland via public API (directly or via a call chain). We can't guarantee monomorphism here (for example, a user can give both common and arrow functions as arguments). So maybe in these casesarguments
should be copied before the call, as in many other lib places.func
is a declared public method. Here is a small danger still: a user can add a property to the public function at any time. Therefore, maybe these cases are worth to be refactored as well.func
is a private function (declared or given as direct/indirect argument). The monomorphism of thisfunc
should be checked across all the code base chains.IV. Occurrences in Node.js libs
func
is an argument gotten from a userland via public API(fixed).dns.js
:callback
inmakeAsync()
/asyncCallback()
is given fromexports.lookup()
,exports.lookupService()
and possibly via manyexports.resolve***()
functions(fixing seems to cause performance downgrading)events.js
:this.listener
inonceWrapper()
is given from this chain:_onceWrap()
<-EventEmitter.prototype.once()
orEventEmitter.prototype.prependOnceListener()
.(this anonymous ones are one-time unoptimizable functions)fs.js
:cb
in anonymous /makeCallback()
is given from many (more than 20)fs.***()
(i.e.exports.***()
) functions.repl.js
:this.completer
inREPLServer.prototype.complete()
is given from this chain:REPLServer()
<-exports.start()
._stream_readable.js
:stream[method]
inReadable.prototype.wrap(stream)
is given directly.(under consideration).internal/util.js
:exports.deprecate(fn, msg, code)
andfn
inexports.deprecate()
is given fromutil.deprecate()
func
is a declared public methodnet.js
:stream.Duplex.prototype.write
inSocket.prototype.write()
util.js
:exports.format
indebugs[set]()
/exports.debuglog()
util.js
:-exports.format
inexports.log()
_http_server.js
:this.writeHead
inServerResponse.prototype.writeHeader()
internal/process.js
:require('util').format
inprocess._rawDebug()
/setupRawDebug()
func
is a private function. Most detected cases seem to be OK.In addition, there are some cases I could not define which group they belong to and if they are monomorphic. I can't trace all the chain here (got out of memory and stack overflow in my mind).
(fixed)_tls_wrap.js
:this._parent[name]
intls_wrap.TLSWrap.prototype[name]()
internal/cluster/utils.js
:fn
inmodule.exports.internal()
internal/cluster/child.js
:close
inhandle.close()
/shared()
internal/cluster/worker.js
:this.destroy
inWorker.prototype.kill()
.internal/cluster/worker.js
:this.process.send
inWorker.prototype.send()
Sorry for my bad English an overall mess in explanation.
// cc @nodejs/v8 , @bmeurer , @vhf
The text was updated successfully, but these errors were encountered: