-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Improve zoom/pan performance by avoiding redundant tag class and label width recomputation #11834
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: develop
Are you sure you want to change the base?
Conversation
|
Thanks for taking a closer look — glad to hear this is an improvement. I’ll take a closer look at whether we can avoid repeatedly indexing into the entity tags during redraws, or restructure the cache to operate at the entity or tag-set level instead. I’ll report back once I have a clearer picture. |
|
Thanks for the detailed profiling — that was very helpful. I’ve pushed a follow-up commit that adds a WeakMap cache keyed by the entity’s tag object, so getClassesString() can return early without repeatedly indexing t[k] during redraws. Let me know if you’d like me to try a different cache key or gather additional traces. |
|
Could you add some guidance on how to test this properly? And also add some test results here. Is this a case that will be visible in flame charts? |
|
@tordans, How to test: What to look for: Observed results: Let me know if you’d like screenshots attached or traces collected for a specific scenario. |
This comment was marked as resolved.
This comment was marked as resolved.
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.
this is a great start, nice work. some thoughts below:
modules/svg/tag_classes.js
Outdated
| var byBase = _classCache.get(t); | ||
| if (!byBase) { | ||
| byBase = new Map(); | ||
| _classCache.set(t, byBase); |
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.
currently your cache (WeakMap<Tags, Map<string, string>>) represents this hierarchy: tags → oldClassName → newClassName.
This leads to a few issues:
- the cache hit rate will be very low, because whenever the user hovers or selects a feature, the class name is updated (
.selectedor.hoveris added/removed) - your cache will grow in size quickly, and store many no-op transformations:
To solve these issues, I think you could only cache a weak-mapping of tags → className (i.e. WeakMap<Tags, string>)
This will require some refactoring, so that we have a pure function which is simply f(tags) → className.
for example, something like this:
let cache = new WeakMap();
tagClasses.getClassesString = function(t, oldClassName) {
const oldClassNamestoKeep = oldClassName.split(' ').filter(…).join(' ');
let newClassNames = cache.get(t);
if (newClassNames === undefined) {
newClassNames = getClassesStringPure(t); // refactor everything else into this new function
cache.set(t, newClassNames);
}
return oldClassNamestoKeep + ' ' + newClassNames;
}
modules/svg/tag_classes.js
Outdated
| // Cache computed class strings by tag object identity + base class. | ||
| // Safe for zoom/pan because tags do not change, and if tags change | ||
| // the graph creates new tag objects (cache miss => recompute). | ||
| var _classCache = new WeakMap(); |
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.
this needs to be in the global scope, not inside svgTagClasses(). otherwise the cache will get wiped on every render, since svgTagClasses() is frequently recreated
| @@ -789,19 +789,31 @@ export function svgLabels(projection, context) { | |||
| const _textWidthCache = {}; | |||
| export function textWidth(text, size, container) { | |||
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.
for the textWidth changes (which are unrelated to the other optimisation): 👍 from me, re-using a single DOM element is definitely more efficient than creating hudnreds of temporary elements whenever the map is panned/zoomed
|
Thanks for the detailed feedback — that makes sense.
|
|
if it helps, 556fbb2 is one way to implement the suggestions above |
|
Thanks for the pointer to 556fbb2 — that helped. I’ve updated the implementation to: • move the cache to module scope Let me know if this matches what you were suggesting, or if you'd prefer it structured differently. |

This PR addresses the zoom and pan performance issue described in #11832.
Problem
Profiling during zoom/pan interactions showed significant time spent repeatedly:
These operations were executed on every redraw, even when the underlying data did not change.
Solution
This change reduces redundant work during redraws by:
Result
tagClasses.getClassesStringno longer dominate redraw timeTesting
(zoom level 18 near 53.382847, -1.470167)