6x slower than V8 on Function.prototype.apply (affects Angular)
Categories
(Core :: JavaScript Engine, defect, P1)
Tracking
()
Tracking | Status | |
---|---|---|
firefox114 | --- | fixed |
People
(Reporter: mstange, Assigned: jandem)
References
(Blocks 1 open bug)
Details
(Whiteboard: [sp3])
Attachments
(2 files)
We're seeing 1.1% of the time on the TodoMVC-Angular benchmark spent in fun_apply
and fun_call
(excluding JS callee time). We've also seen this issue on TodoMVC-Backbone (in dispatch
, 0.7% overall) and on React-Stockcharts (in invokeGuardedCallback
, 0.5% overall).
The JS-self time of the Angular function invoke
is 3.8x higher in SM than in V8.
SM (124 samples): https://share.firefox.dev/3H65gh6
V8 (33 samples): https://share.firefox.dev/3V00zLn
invoke(targetZone, callback, applyThis, applyArgs, source) {
return this._invokeZS
? this._invokeZS.onInvoke(
this._invokeDlgt,
this._invokeCurrZone,
targetZone,
callback,
applyThis,
applyArgs,
source
)
: callback.apply(applyThis, applyArgs);
}
invoke
is called around 1800 times during the benchmark. 10% of the calls are with a one-element arguments array, the remaining 90% are with applyArgs
being undefined
.
Here's a microbenchmark which calls apply
200000 times:
function invoke(callback, applyThis, applyArgs) {
callback.apply(applyThis, applyArgs);
}
function method(a) {}
var thisObj = {};
function benchmark() {
for (var c = 0; c < 200000; c++) {
invoke(method, thisObj, undefined);
}
}
var start = performance.now();
benchmark();
var end = performance.now();
print(`took ${end - start}ms`);
At this iteration count it shows a 6x difference:
% js --only-inline-selfhosted apply.js
took 19.509765625ms
% d8 --no-turbo-inlining apply.js
took 3.25ms
Most of the cost seems to be the re-entry into JIT land. It would be good to make apply
work without leaving the JIT.
Updated•1 year ago
|
Assignee | ||
Comment 1•1 year ago
|
||
We optimize apply
when called with an arguments object or an array for the second argument. Calling it with null
or undefined
is equivalent to just calling the target function with the passed this
value and zero arguments.
The difficulty is that you're seeing calls passing either an array or undefined
. This is fine for Baseline, but because we don't have call ICs in Ion, it's annoying if we have multiple Baseline stubs because we can't transpile then. Trial inlining could potentially help though..
Assignee | ||
Comment 2•1 year ago
•
|
||
I have an initial patch for apply
with null
/undefined
. Let me see if this helps Speedometer or if it's making the problem worse by having more polymorphic Call ICs...
Assignee | ||
Comment 3•1 year ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #2)
Let me see if this helps Speedometer or if it's making the problem worse by having more polymorphic Call ICs...
Looking at the number of calls to js::fun_apply
on Speedometer 3, it reduces it by about 10-15%. It's able to get rid of most of the calls in Angular.
Updated•1 year ago
|
Assignee | ||
Comment 4•1 year ago
|
||
In this case we can just call the target function with the provided this
value
and no arguments.
This gets rid of about 10-15% of calls to js::fun_apply
on Speedometer, mostly
from the Angular test.
Comment 6•1 year ago
|
||
bugherder |
Reporter | ||
Comment 7•1 year ago
|
||
This optimization successfully removed invoke
and invokeTask
from the top 20 functions (sorted by diff) on the Angular comparison report.
Thanks for the incredibly fast response!
Description
•