-
Notifications
You must be signed in to change notification settings - Fork 44
[WIP] Remove performance bottleneck in active element preservation #137
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
base: main
Are you sure you want to change the base?
Conversation
… precomputing the active element parent path and doing an array inclusion check instead.
Note that activeElement defaults to body so it will still be checking constantly if the element contains body element. |
@MichaelWest22 Ohh, you're right! That explains the perf win in the existing benchmarks then. Maybe we don't need a separate benchmark. |
having a body exclude check would probably give a simpler perf boost. But this may not help when a real item is active and there could be a perf hit then. I wonder if contains could perform better if its not being passed a giant body tag??? That could be worth testing to confirm maybe |
@MichaelWest22 So maybe this could be optimized a bit more by checking if the active element is within the oldNode at startup, and only populating the array if it is. Then we only do the test if the array is nonempty. BTW, I also looked into doing Set inclusion instead of Array inclusion, but it turns out that's only faster with much larger collection sizes. Array is faster for smaller collections. |
@MichaelWest22 I don't think that its more expensive to pass a body tag with a million children, than a leaf node with none, provided we're just testing for array inclusion. But this is just a hunch. In other words, DOM elements are passed by reference, not by value. |
You could get your createActiveElementAndParents routine to have the while loop to run while elt !== oldNode so it will return the array only if its valid with active element in the oldNode. Then add a early return [] if elt is null inside the loop for the other case maybe |
@MichaelWest22 Good suggestions, I fiddled with them a bit and ended up with this: let activeElementAndParents = [];
let elt = document.activeElement;
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
while (elt) {
activeElementAndParents.push(elt);
if (elt === oldNode) break;
elt = elt.parentElement;
}
}
return activeElementAndParents; Perf win is now 35% on the benchmark. This is about as good as I can imagine getting it, unless you have any more ideas? |
function createActiveElementAndParents(oldNode) {
/** @type {Element[]} */
let activeElementAndParents = [];
let elt = document.activeElement;
while (elt !== oldNode) {
if (!elt) return []
activeElementAndParents.push(elt);
elt = elt.parentElement;
}
return activeElementAndParents;
} Yeah something like this could be slightly more efficient as it avoids that extra contains call even though it is only called once so I don't think you will notice any different in full run performance really. |
@MichaelWest22 Yeah I'm okay with a single But yeah I don't think its so important that |
It turns out that
Element.contains
is really slow, so we get a big perf win by precomputing the active element parent path and doing an array inclusion check instead.I'm marking this PR as WIP because I want to add a benchmark for this before merging. None of the existing perf benchmark have active elements, so they don't really capture the slowdown. But considering how impactful this seems to be, a benchmark seems like a good idea. It's about an order of magnitude in my BARD Tracker app!
Funny enough, though, I'm already seeing perf increases of around 10-35% in the existing benchmarks, even though they don't contain any active elements!
Closes #134