selection_select in Charts-observable-plot is 12x slower than Chrome on Android
Categories
(Core :: JavaScript Engine: JIT, defect, P2)
Tracking
()
People
(Reporter: denispal, Unassigned)
References
(Blocks 1 open bug)
Details
(Whiteboard: [sp3])
We seem to spend a lot of time just adding new slots here.
Firefox: https://share.firefox.dev/4eT5qao
Chrome: https://share.firefox.dev/3BWWBxK
Function Definition:
https://github.com/mozilla/Speedometer/blob/537d5769ef9ef3fa9a236884eae4d109c0628833//resources/charts/dist/assets/plot-37d2a5fb.js#L894
function selection_select(select2) {
if (typeof select2 !== "function")
select2 = selector(select2);
for (var groups2 = this._groups, m = groups2.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group2 = groups2[j], n = group2.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
if ((node = group2[i]) && (subnode = select2.call(node, node.__data__, i, group2))) {
if ("__data__" in node)
subnode.__data__ = node.__data__;
subgroup[i] = subnode;
}
}
}
return new Selection$1(subgroups, this._parents);
}
| Reporter | ||
Updated•1 year ago
|
Updated•1 year ago
|
| Reporter | ||
Comment 1•1 year ago
|
||
Also seen in TodoItem from TodoMVC-WebComponents:
Firefox: https://share.firefox.dev/4eTfoZq
Chrome: https://share.firefox.dev/4heug67
Updated•1 year ago
|
Comment 2•1 year ago
|
||
In the Windows profiles from https://shell--speedometer-preview.netlify.app/?suite=Charts-observable-plot&iterationCount=100 , I don't see growSlots being called during selection_select at all: https://share.firefox.dev/4f7eMzw
I wonder why we call it on Android but not on Windows.
Comment 3•1 year ago
|
||
Comparing the Android profile here to the Windows profile here, the Android profile is calling the add_property hook on a lot of different kinds of binding (SVG(Line|Path|Rect|Text|Title|Circle)Element_Binding), whereas the Windows profile only calls HTMLParagraphElement_Binding::_addProperty. I don't know why that would be the case, but it explains why we might not be seeing growSlots being called here: if HTMLParagraphElement_Binding already has an unused slot, there's no need to allocate. (Glancing at the code, I assume that this is all being done for subnode.__data__ = ..., so we're presumably only adding one property to any given object.)
So the interesting question is why we're seeing SVG stuff on Android, but HTMLParagraph on Windows.
Comment 4•1 year ago
|
||
(In reply to Iain Ireland [:iain] from comment #3)
whereas the Windows profile only calls
HTMLParagraphElement_Binding::_addProperty.
I'm not sure if we know this for sure - the compiler may have inline-code-folded multiple _addProperty methods. Actually I think that's pretty likely, because I don't think the test would be using paragraph elements in the chart.
Comment 5•1 year ago
|
||
I see JSCLASS_HAS_RESERVED_SLOTS(DOM_INTERFACE_PROTO_SLOTS_BASE) in the bindings for these SVG elements: path, rect, circle, text, title
And DOM_INTERFACE_PROTO_SLOTS_BASE is zero.
So for those we would expect to see growSlots on Windows as well, I think.
Comment 6•1 year ago
|
||
(In reply to Markus Stange [:mstange] from comment #4)
I'm not sure if we know this for sure - the compiler may have inline-code-folded multiple
_addPropertymethods. Actually I think that's pretty likely, because I don't think the test would be using paragraph elements in the chart.
That's a good point. It does make me wonder why we haven't been able to deduplicate identical functions in the same way for the Android build.
(In reply to Markus Stange [:mstange] from comment #5)
I see
JSCLASS_HAS_RESERVED_SLOTS(DOM_INTERFACE_PROTO_SLOTS_BASE)in the bindings for these SVG elements: path, rect, circle, text, title
AndDOM_INTERFACE_PROTO_SLOTS_BASEis zero.
So for those we would expect to seegrowSlotson Windows as well, I think.
I believe you're looking at the class definition for the prototype, not for the SVG wrapper objects themselves. I see 1 reserved slot for every SVG instance class. I think this searchfox query should find most of the DOM classes with a different number of reserved slots.
I would expect classes with one reserved slot to get AllocKind::OBJECT2, meaning that they would have one unused slot available. Adding two custom properties would require us to grow the slots.
It might be interesting to run this test with a patch that dumps obj->getClass()->name here to give us a sense of what kinds of objects we're actually seeing, and in what quantities.
Updated•1 year ago
|
Updated•1 year ago
|
Comment 7•1 year ago
|
||
I took a quick look at this in a local build. I added a patch to dump a message in js::NativeObject::setShapeAndAddNewSlot indicating the class, whether we were storing into a fixed slot or a dynamic slot, and (in the case of dynamic slots), whether we had to reallocate the slots array:
2 dynamic: WebExtensionPolicy grow
4 fixed: WebExtensionPolicy
20 dynamic: SVGSVGElement grow
20 fixed: SVGSVGElement
1078 fixed: SVGLineElement
2761 fixed: SVGPathElement
3961 fixed: SVGTextElement
6060 fixed: SVGCircleElement
13246 fixed: SVGRectElement
19310 fixed: SVGTitleElement
We see tens of thousands of SVG elements adding a new fixed slot, which does not require reallocation. Across a run of charts-observable-plot, we only allocate slots 22 times.
After taking into account Markus's point about code deduplication, this information is consistent with the Windows profile analyzed above: we call the hook a lot, but we don't see any samples reallocating slots. But that's not what shows up in Denis's Android profile. Something weird is going on there.
Denis, can you take a look at what Android is doing here? It doesn't make sense for there to be samples in growSlots.
For reference, my hacky patch:
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1476,16 +1476,32 @@ bool js::AddSlotAndCallAddPropHook(JSCon
HandleValue v, Handle<Shape*> newShape) {
MOZ_ASSERT(obj->getClass()->getAddProperty());
MOZ_ASSERT(newShape->asShared().lastProperty().isDataProperty());
RootedId id(cx, newShape->asShared().lastProperty().key());
MOZ_ASSERT(!id.isInt());
uint32_t slot = newShape->asShared().lastProperty().slot();
+
+ char buf[100];
+ sprintf(buf, "/tmp/addprop-%d", getpid());
+ FILE* f = fopen(buf, "a");
+
+ uint32_t numFixed = newShape->asShared().numFixedSlots();
+ if (slot < numFixed) {
+ fprintf(f, "%s: fixed\n", obj->getClass()->name);
+ } else {
+ uint32_t dynamicSlotIndex = slot - numFixed;
+ fprintf(f, "%s: dynamic (%s)\n",
+ obj->getClass()->name,
+ dynamicSlotIndex >= obj->numDynamicSlots() ? "grow" : "fits");
+ }
+ fclose(f);
+
if (!obj->setShapeAndAddNewSlot(cx, &newShape->asShared(), slot)) {
return false;
}
obj->initSlot(slot, v);
return CallAddPropertyHook(cx, obj, id, v);
}
(Clearing js-perf-next for now because this is still just an interesting observation, not actionable.)
| Reporter | ||
Comment 8•1 year ago
|
||
Results on Android:
31 JSWindowActorChild: dynamic (fits)
22 JSWindowActorChild: dynamic (grow)
3 JSWindowActorChild: fixed
6060 SVGCircleElement: fixed
1077 SVGLineElement: fixed
2760 SVGPathElement: fixed
13244 SVGRectElement: fixed
19 SVGSVGElement: dynamic (grow)
19 SVGSVGElement: fixed
3960 SVGTextElement: fixed
19309 SVGTitleElement: fixed
| Reporter | ||
Comment 9•1 year ago
|
||
The original profile has a lot of time spent in malloc lock contention. Here is an updated profile with the patch from bug 1913757. This reduces the difference down to 6x where we are spending most of our time in mozilla::dom::PreserveWrapper.
Comment 10•1 year ago
|
||
I think it's safe to dupe this to bug 1844878, although since it does look like we're mostly only adding one property to a given wrapper, the patch currently attached to that bug won't solve the problem on its own.
Description
•