-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Misc perf improvements #1772
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
Misc perf improvements #1772
Conversation
means we get a lot more caching, and a lot less DOM manipulation and getBoundingClientRect
@@ -481,13 +481,6 @@ lib.containsAny = function(s, fragments) { | |||
return false; | |||
}; | |||
|
|||
// get the parent Plotly plot of any element. Whoo jquery-free tree climbing! | |||
lib.getPlotDiv = function(el) { |
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.
Beautiful commit.
Note that Lib.isPlotDiv
is currently used in W2. No such problem for Lib.getPlotDiv
.
@@ -83,7 +89,7 @@ Titles.draw = function(gd, titleClass, options) { | |||
} | |||
|
|||
var el = group.selectAll('text') | |||
.data([0]); | |||
.data(elShouldExist ? [0] : []); |
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.
Nice 🍺
@@ -8,6 +8,8 @@ var credentials = require('../../build/credentials.json'); | |||
var Lib = require('@src/lib'); | |||
var d3 = Plotly.d3; | |||
|
|||
require('./perf'); |
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.
cc @rreusser would be nice to have something like that in https://github.com/rreusser/plotly-mock-viewer
Moreover, @dfcreative's brilliant ✨ https://www.npmjs.com/package/fps-indicator ✨ would be a nice addition to our dev tools.
// } | ||
// return h; | ||
// } | ||
function nodeHash(node) { |
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.
Interesting patch. Do you have any benchmark results comparing old and new Drawing.bBox
?
If you're concerned about memory usage, you might want to consider setting maxSavedBBoxes
to something less than 1e4
. 10000 text nodes on one page sounds a bit much.
cc'ing @monfera @rreusser who might have experience doing stuff like this.
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.
Do you have any benchmark results comparing old and new Drawing.bBox?
I don't have any explicit benchmarks, but putting in a few console.log
statements you can see that previously during redraws (and especially interactions like dragging axis ends) we were only actually using the cache in a few cases, mostly we were redoing the cloneNode
and getBoundingClientRect
. Now we hardly ever need to do that. The key is that managing data-bb
so we can safely use the cache is really hard and we weren't doing it well. This way there's nothing to manage, and nodeHash
is very fast (as long as I don't actually hash the resulting string 🤔 ).
10000 text nodes on one page sounds a bit much.
Yes, perhaps I could drop the limit a bit, but two things to note:
- I'm not storing text nodes, just a lookup object of
{nodeHash: bBox}
wherebBox
is a plain object, my main concern is thatnodeHash
itself can be quite a long string, though I guess abBox
object of 6 numbers can also take non-negligible memory. - A large number of text nodes is exactly when this would be most useful, and wiping the cache will kill this whole gain. Perhaps I could take advantage of
Object.keys
being ordered in insertion order (usually? always?) and drop the oldest 50% of cache entries or something if we hit the limit?
Looks great!
|
return function() { | ||
gd._mouseDownTime = 0; // ensure independence from any previous clicks | ||
return doubleClick(getDragger(subplot, directions)); | ||
}; |
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.
Man, this one was brutal - the key was that (locally, in chrome, when running multiple tests) it was seeing a previous click (from the same test, despite the fact that it still cared whether another test had been run before this one) too close to the first click of the doubleclick, and ended up responding as if there were two doubleclicks - which I still don't quite understand, I didn't think a triple-click would look like two doubles, but anyway this works and we can use this in the future (similar to your trick with gd._lastHoverTime
@etpinard ) to cut down on unnecessary delays in the test suite.
For future reference @etpinard since we talked about potentially doing this, I went off on a dead end for a while when I noticed that calling Plotly.purge(gd)
during destroyGraphDiv
caused all sorts of problems... That turns out to be unrelated, that sometimes there are some things still waiting to happen on the plot after tests have finished (like rehover
) which will fail (and confusingly attribute the failure to the next test that's run, since it's happening async) if we've purged the plot of attributes like emit
but they still complete without failure if all we've done is remove the gd
from the DOM.
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.
Thanks for writing down the details.
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.
The only reason we needed two initInteractions during Plotly.plot is so the second one could pick up the `onmousemove` from the first to cache as `initialMouseMove`, but it works fine now to remove that junk entirely.
Some profiling on small charts: All results from Chrome on my mac with no profiles running. Running profiles adds ~5% to these times with no screenshots, ~10% with screenshots (interestingly the profiling penalty seems bigger even fractionally before the change). All results are averaging ~10 runs of using mock
using a chart with ~30 variable-size bubbles, gradient fill, long category names:
|
(offline weekend 💃 from @etpinard ) |
@@ -141,6 +140,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index) { | |||
|
|||
dragElement.init(dragOptions); | |||
|
|||
shapePath.node().onmousemove = updateDragMode; |
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.
Referencing the ticket for annotation edits -> #679
@@ -57,15 +62,11 @@ dragElement.init = function init(options) { | |||
startY, | |||
newMouseDownTime, | |||
dragCover, | |||
initialTarget, | |||
initialOnMouseMove; |
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.
Amazing. Thanks for 🔪 that!
A bunch of perf improvements. The biggest one is changing how we cache
Drawing.bBox
results ea883e6 - instead of having an element keep track of its own caching, I changed it to just using the contents of the node (itsinnerHTML
plus itsstyle
andtext-anchor
- I'm 90% sure this is sufficient to determine the bbox but needs a bit more testing). This means we're not always invalidating the cache, whether accidentally or out of an abundance of caution, and in fact even unrelated elements that have the same contents and styling can use the same bbox (such as matching labels on different axes).Another fairly significant one is removing an unnecessary double-draw of ticks and grids during
Plotly.plot
b4a2f4d - no idea where that came from but it doesn't seem to cause any problems to pare that down.Two smaller ones:
Lib.getPlotDiv
032cf7c - this was by far the most intrusive change (I had to passgd
around to a bunch of places that didn't have it before), for the smallest benefit, but it turns outinstanceof HTMLElement
(which is actually inLib.isPlotDiv
which I didn't get rid of... but that one calls it once andgetPlotDiv
called it in a loop) could be kind of slow.Also I added a perf tester
timeit
to the global scope in the test dashboard, just for convenience.I'd like to get #1767 merged before finishing this so I can bring the test improvements in here.