weisJ/jsvg

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:

  1. Change the ViewBox to be an Area. In this case, unless the clip path is already a Rectangle, it will call intersectByArea, which will check if the result is rectangular, and return a Rectangle if so.
  2. Change the ClipPath#clipShape or ShapedContainer#untransformedElementShape to make an Area of the resulting Path2D, check if it's rectangular (Area#isRectangular), and if so, return Area#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?

weisJ commented

1. Clip paths

...
Two solutions come to mind:

  1. Change the ViewBox to be an Area. In this case, unless the clip path is already a Rectangle, it will call intersectByArea, which will check if the result is rectangular, and return a Rectangle if so.
  2. Change the ClipPath#clipShape or ShapedContainer#untransformedElementShape to make an Area of the resulting Path2D, check if it's rectangular (Area#isRectangular), and if so, return Area#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 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.

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!