d3 / d3-axis

Human-readable reference marks for scales.

Home Page:https://d3js.org/d3-axis

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Slowdown due to superficious DOM modifications

nponeccop opened this issue · comments

Some attributes are re-set to the same values on pre-existing elements instead of setting once only on freshly added elements:

https://github.com/d3/d3-axis/blob/master/src/axis.js#L85-110

Profiler of MS Edge browser shows that some DOM operations in existing axis code take as much as 0.5 ms, which is large given the 16.7 ms total time bugdet. Firefox is slower, Chrome is faster, and the story on mobile devices may be totally different, but still.

A dirty hack shows significant increase in frame rate, it seems that setting attributes on text nodes is especially expensive:

https://github.com/streamcode9/svg-time-series/blob/626ddf9bed104bb457217f2183084b91a4b4261f/benchmarks/d3-myaxis/index.js#L118-142

While I added separate axisUp method for incremental updates, it's a proof of concept code to see if framerate can be improved. A cleaner approach of setting attributes only on what "enters" is certainly possible.

Also, grouping and stylesheet manipulation using CSS DOM can be used to avoid repeatedly setting the same attributes on added nodes. They can be inherited from parent element or defined once in declarations instead.

What do you think? Can we for example avoid setting .transform on many nodes, and set it once on a CSS group element or something like that?

None of those modifications are superfluous in the general case. Various changes to the axis configuration can require those updates:

  • The axis tick size can change.
  • The axis orientation can change.
  • A prior axis transition can be interrupted, preventing the tick opacity from reaching 1.

(You also removed a few essential features of the axis, like a default appearance and cross-fading ticks during a transition.)

You could move a few things to entering elements if you require that the axis orientation doesn’t change. But then you’d have to document that the way to change the axis orientation is to remove the old axis and add a new axis, rather than just re-applying a new axis with the new orientation.

You could avoid setting the entering line’s x2 and entering tick’s x attributes in the case that you’re updating the axis instantaneously (since they are subsequently set on enter + update), but this is a relatively minor optimization.

You could avoid setting a few attributes separately on each tick, but it would require restructuring the axis so that the tick labels and tick lines are grouped separately, rather than grouping the label and line for each tick together. Which would mean you’d need to set the transform attribute on twice as many elements to move the ticks into place, or using attributes to position the tick labels and lines rather than transforms.

I could maybe see changing the axis to assume a fixed orientation; see e1d90ce. (I also changed it to only define default styles the first time the axis is applied.) I am sure it will trip up at least some users in the future, but maybe that’s acceptable if documented. I could also maybe see the axis detecting an orientation change and patching the difference just in that case, though I fear that adds complexity to the implementation…

I’ve merged the aforementioned optimizations and documented the requirement that the axis orientation is fixed. If you have other suggested optimizations, please let me know. Thank you.

Can you update http://bl.ocks.org/mbostock/db6b4335bf1662b413e7968910104f0f to use alpha 50, as it incorporates the above performance improvements?

Wrapping draw calls with d3.timeout to avoid multiple updates per frame improves framerate in that example too, but makes code bulky and less suitable for learning. But you could add a note to the block's readme.

I updated it.