svgdotjs / svg.js

The lightweight library for manipulating and animating SVG
https://svgjs.dev
Other
11.01k stars 1.07k forks source link

Bad Performance for text() or plain() #1214

Closed Shiuyin closed 3 years ago

Shiuyin commented 3 years ago

We have done some performance analysis when using svg.js text() or plain() elements.

When creating 200 nodes and 6 text() elements per node (=> 1200 text() elements), svg.js takes about 11s to render. This is in an empty side. In contrast using the plain() element, this takes just about 4s. Both take way to much time tho as the vanilla js implementation is done in about 0.01s.

From what we can tell there is a lot of time used in getBBox() and a lot of extra magic going on with newlines, etc. Is there a way to improve this performance to be able to support thousands of text elements and still get usable performance?

Attached you can find the dump of Chrome's performance profiler for both cases.

svg_text_performance.zip

Fuzzyma commented 3 years ago

What was the code to render the text?

Shiuyin commented 3 years ago

Basically it boils down to these:

// 1st attempt: Regular text() element
// rowGroup.text(cell.label).move(currentX, y)

// 2nd attempt: Regular plain() element
// rowGroup.plain(cell.label).move(currentX, y)

// 3rd attempt: vanilla js
// const text = document.createElementNS('http://www.w3.org/2000/svg', 'text')
// text.setAttribute('x', currentX.toString())
// text.setAttribute('y', y.toString())
// text.setAttribute('fill', '#000')
// text.textContent = cell.label
// const svgEl = document.getElementById(id)
// svgEl.appendChild(text)

These are simply called in a loop of those 1200 strings to display.

Fuzzyma commented 3 years ago

Replace move with amove and you should see a dramatic speedup. svg.js positions the text by its upper left corner (as any shape) and NOT its baseline. Thats why the bbox call is needed (which is really slow). amove() will instead position the text by just setting x and y (so by its anchor and baseline) and therefore is faster

Shiuyin commented 3 years ago

That actually helped quite a lot. Thank you. We are now down to 5s for 1200 text() elements and about 0.5s for 1200 plain() elements. So the plain() element is pretty close to the vanillajs but still about 50x slower. Are there any further recommendations you have to improve the performance for large svg's with thousands of text elements?

Fuzzyma commented 3 years ago

You will never achieve the same performance. svg.js allocates objects, calls functions, checks multiple inputs and so on. Instead of amove you can just use attr which saves you one call but that wont do much. If you need performance, create yourself a helper function that uses vanillajs for the heavy lifting and adopt the elements with svg.js later

Shiuyin commented 3 years ago

Ok. Thank you for your answers.