Performance improvement ideas, poor performance compared to Batik for particular SVG file
Closed this issue ยท 2 comments
Hello,
I have recently been evaluating jsvg as an alternative to Batik for an existing application, and in terms of memory usage, it's a vast improvement! ๐
The problem is that with a real-world SVG that I unfortunately can't share, it takes jsvg almost 10 times longer to render it to a Graphics2D
object. The file has some 1,500,000 paths, most referencing a clipPath
.
I've identified two main issues that I would appreciate some input on, I'm open to making a PR if these are acceptable changes to make:
1. Clip paths
This is reliant on SunGraphics2D
being the Graphics2D
implementation.
When rendering a document, the SVGDocument
will set a clip on the "root" Graphics2D
instance. This clip is a ViewBox
, which is a Rectangle
.
Subsequent clips with a ClipPath
will cause a clip intersection (SunGraphics2D#intersectShapes
), and the other shape will be a Path2D
as returned by ShapedContainer#untransformedElementShape
.
As the Path2D
is entirely contained within the ViewBox
, the Path2D
will be used as-is for clipping. This is inefficient, as the SunGraphics2D#validateCompClip
is slow for non-rectangular clip shapes.
Two solutions come to mind:
- Change the
ViewBox
to be anArea
. In this case, unless the clip path is already aRectangle
, it will callintersectByArea
, which will check if the result is rectangular, and return aRectangle
if so. - Change the
ClipPath#clipShape
orShapedContainer#untransformedElementShape
to make anArea
of the resultingPath2D
, check if it's rectangular (Area#isRectangular
), and if so, returnArea#getBounds
instead.
I've tried option 1, and it works very well in my case.
2. Excessive Graphics2D
instance creations
While Graphics2D#create
is a generally quick operation, the cloning of the RenderingHints
takes a little bit of time, especially when done 5 times per node.
In particular, the ShapeRenderer#renderWithPaintOrder
creates 4 instances. I understand it could be necessary in some cases, but in my cases, just using the passed in Graphics2D g
argument and getting rid of the Graphics2D phaseGraphics
produces the same output and significantly faster.
Thoughts?
1. Clip paths
...
Two solutions come to mind:
- Change the
ViewBox
to be anArea
. In this case, unless the clip path is already aRectangle
, it will callintersectByArea
, which will check if the result is rectangular, and return aRectangle
if so.- Change the
ClipPath#clipShape
orShapedContainer#untransformedElementShape
to make anArea
of the resultingPath2D
, check if it's rectangular (Area#isRectangular
), and if so, returnArea#getBounds
instead.I've tried option 1, and it works very well in my case.
I am more inclined to go for solution 2. For one it feels like the broader solution of which more codepaths can profit from. On the other hand changing the type of ViewBox
is ABI breaking which I would like to avoid if possible.
2. Excessive
Graphics2D
instance creationsWhile
Graphics2D#create
is a generally quick operation, the cloning of theRenderingHints
takes a little bit of time, especially when done 5 times per node.In particular, the
ShapeRenderer#renderWithPaintOrder
creates 4 instances. I understand it could be necessary in some cases, but in my cases, just using the passed inGraphics2D g
argument and getting rid of theGraphics2D phaseGraphics
produces the same output and significantly faster.
You are right that there is opportunity to reduce the amount of Graphics2D
instances being created. Certainly not all phases need their own graphics context. I went for the current solution because it was easier to reason about correctness
this way and to deduplicate checks. The problem at hand is that the individual phases will modify the graphics object (e.g.) for transparency or vector effects. I could image a more lightweight alternative by saving the relevant state of the Graphics2D
object and reset it manually afterwards.
I'm open to making a PR if these are acceptable changes to make
I'd be happy for a PR. I very much appreciate your input.
@weisJ I tried out the 1.2.0 version and it works great. Thank you for the quick responses, much appreciated!