[clipart]Cameron Adams decided to benchmark Flash, SVG, Canvas and HTML5 using a particle engine he created. Not surprising (to me anyway), the SVG scores were the worst of the bunch. This stands to reason: does each particle really need to be a DOM element? Nonetheless, I decided to see what I could do to make the SVG scores suck less. I thought I'd use it as an opportunity to also teach some techniques.

Update: Added some browser scores for Nov 2011

Particle Creation

Cameron initializes the particles this way:

	var domNode = document.createElementNS(SVG_NS, "circle");
	var benchmark = document.getElementById("benchmark");
	benchmark.appendChild(domNode);
	
	// Set initial position to middle of screen
	domNode.setAttribute("cx", x + PARTICLE_RADIUS);
	domNode.setAttribute("cy", y + PARTICLE_RADIUS);
	domNode.setAttribute("r", PARTICLE_RADIUS);
	
	// Set colour of element
	domNode.setAttribute("fill", COLORS[Math.floor(Math.random() * COLORS.length)]);

That is, he creates a svg:circle element, then appends it to the DOM, then modifies its attributes. Every time an attribute is changed in the DOM it has the potential to cause a layout/render change. Thus, it makes more sense (to me) to attach the circle to the DOM only after you're done initializing the element (i.e. after all the setAttribute calls).

This doesn't affect the frame-rate, since creation only happens at the beginning, but it might affect the startup time of the animation so I thought I'd drop it in this post.

DOM Animation - the Naive Approach

Next, he has a function that is called as fast as possible to animate the particles. The animate function does a simple loop over all particles and calls a draw() function. The particle's draw function does this:

		// lots of other JS code here
		domNode.setAttribute("cx", nextX + PARTICLE_RADIUS);
		domNode.setAttribute("cy", nextY + PARTICLE_RADIUS);

The problem with this again is that each DOM call can cause a re-render. This means if you have 500 particles, there are 1000 DOM calls affecting the rendering with every call to animate(). Depending on how stupid the browser is, this could be expensive.

Original Results
Browser Frame Rate
Firefox 3.6 (OSX) 1.75 fps
Firefox 3.7 Nightly (OSX) 1.9 fps
Firefox 8 Beta (OSX) 3.8 fps
Opera 10.10 (OSX) 12.5 fps
Opera 10.50 (OSX) 14.5 fps
IE9 Preview 1 (Win7 VM) 28 fps
Safari 4.0.5 (OSX) 32 fps
Chrome 5 Nightly (OSX) 40 fps
Chromium 17 Nightly (OSX) 55 fps

Suspension

My first attempt was to use suspendRedraw() and unsuspendRedraw() around the for-loop that calls particle.draw(). This is the SVG-recommended way to do a bunch of operations 'off-screen' and then have the results updated in one-shot. This did have an effect on Firefox's frame rate.

But it didn't really seem to affect WebKit's performance at all. Looking at the WebKit source (svg/SVGSVGElement.cpp) it's pretty easy to see why:

unsigned SVGSVGElement::suspendRedraw(unsigned /* maxWaitMilliseconds */)
{
    // FIXME: Implement me (see bug 11275)
    return 0;
}

Maybe WebKit will one day implement this and the technique will become more broadly useful. [Update Nov 2011: This has been implemented in WebKit]

SuspendRedraw Results
Browser Frame Rate
Firefox 3.6 (OSX) 3.2 fps
Firefox 3.7 Nightly (OSX) 3.4 fps
Firefox 8 Beta (OSX) 8 fps
Opera 10.10 (OSX) 8.2 fps
Opera 10.50 (OSX) 23.5 fps
IE9 Preview 1 (Win7 VM) N/A
Safari 4.0.5 (OSX) 32 fps
Chrome 5 Nightly (OSX) 40 fps
Chromium 17 Nightly (OSX) 45 fps

NOTE: IE9 Preview 1 has not implemented suspendRedraw() and the demo does not run.

Manually Doing Off-Screen Rendering

I then went and did something crazy.

I added a svg:g (group) element to the particle test, added all the particles to it. Then with each call to render, I remove the <g> from the DOM, call all the particle draw() functions, then re-add the <g> to the DOM.

This had a dramatic effect on Firefox's performance, but it seemed to slow down Safari and Opera. Not sure why that would be, but I guess they have much more sensible ways of updating the rendered state of the DOM than Firefox so that removing and adding an element to the DOM becomes more expensive.

Manual Offscreening Results
Browser Frame Rate
Safari 4.0.5 (OSX) 12.75 fps
Opera 10.10 (OSX) 14 fps
Firefox 3.6 (OSX) 21.5 fps
Firefox 3.7 Nightly (OSX) 21.5 fps
Opera 10.50 (OSX) 23.5 fps
IE9 Preview 1 (Win7 VM) 25 fps
Chrome 5 Nightly (OSX) 26.5 fps
Firefox 8 Beta (OSX) 37 fps
Chromium 17 Nightly (OSX) 49 fps

Conclusion

This blog post is really just an attempt to share the knowledge about some of the challenges in using SVG for high-performance demos. Basically don't change the live DOM if you can avoid it.

Note that I still stand by my original statement that SVG is not the right technology for particle effects unless you need mouse interactivity. This type of demo is much more suitable for HTML5's <canvas> element. Let's hope IE9 also implements a GPU-accelerated version of this element too!

§757 · March 22, 2010 · Software, SVG, Technology, Tips, Web · Tags: , , , · [Print]

21 Comments to “Know The Beast”

  1. Jeff says:

    Update: Added fps results for all browsers. It felt good to be able to add a row for IE9, let me tell you!

  2. Hi Jeff.

    I’d be interested to know the effect of using the SVG DOM interfaces to set the cx/cy attributes of the circle elements, i.e.:

    domNode.cx.baseVal.value = nextX + PARTICLE_RADIUS;
    domNode.cy.baseVal.value = nextY + PARTICLE_RADIUS;

    In Batik I know that using the SVG DOM like this doesn’t buy you anything unfortunately, though (probably because it eagerly stringifies the attribute values when you set them).

    One thing that I have found helps with Batik, if you’re in the situation where you might set an attribute to be the same value, is to compare it against the current value and skip setting it if it is the same. This is because setting a relevant attribute like cx or cy will always cause the internal graphics node to be rebuilt.

    Finally, you might like to test using a transform on the circle instead of the two separate cx/cy attributes. This might cause only a single relayout operation (at the expense of a bit more parsing of the “translate(x,y)” value).

  3. Jeff says:

    @Cameron: That’s a good point, modifying the transform attribute in one shot should show some performance gains. However, I think I’ve run out of steam on this tonight 🙂

  4. Volker says:

    Hi Jeff,

    nice comparison. However, I’m surprised to see Firefox being that far behind (at least in the naive approach), because with our app we saw a great improvement for Firefox 3.6 which made it almost as fast as Safari 4 and not being 10 times slower anymore.

    Do you know other sources for hints on improving SVG performance? I’m asking cause I’d like to learn more on this and to have a place where I could share our own findings. I also know that G. Wade Johnson gave a session on SVG performance the last SVG open. So there is some material around.

    Cheers,
    Volker

  5. Great work/analysis!

    I’m also curious if it would be faster to create USE elements instead of new circles. I believe I read somewhere that Opera at least has internal optimizations for them…

  6. Jeff says:

    Lars Gunther created a Mozilla bug to track this – maybe something will come of it.

  7. Jeff says:

    @Volker: Other than Wade’s work, I’m not aware of a central repository for this kind of work, sorry.

    @Alexis: Good idea about <use>. I’m curious if it would make a difference for simple circles, since you would still have to change the x,y (or the transform as Cam suggested) of each element in the DOM. But not curious enough to try it out today 🙂

  8. Jeff says:

    Ok, tried the transform approach and here are the results:

    Transform Results
    Browser Frame Rate
    Firefox 3.6 (OSX) 3 fps
    Firefox 3.7 Nightly (OSX) 3.4 fps
    Opera 10.10 (OSX) 14 fps
    Opera 10.50 (OSX) 14.5 fps
    IE9 Preview 1 (Win7 VM) 28 fps
    Safari 4.0.5 (OSX) 30 fps
    Chrome 5 Nightly (OSX) 30 fps
  9. Brad Neuberg says:

    Hi Jeff, what if you also try setting a color for the SVG root background? It might default to transparent when not specified, which could affect the frame rate since more magic has to happen during the rendering to have opacity.

    Also, try using a DocumentFragment during initial startup and see if that helps; you could also perhaps try using cloneNode(true) on your DocumentFragment, updating each of the x/y values on each frame before and then adding it back to the DOM.

    I’m not sure if Firefox supports it, but you could try setting the shape-rendering CSS property on the SVG root to ‘optimize-speed’, which might turn off anti-aliasing and could speed things up; more info on this at http://www.w3.org/1999/07/06/WD-SVG-19990706/render.html . I don’t yet support this in SVG Web but its something I’d like to bake in at some point.

    Other things that could matter: see if setting an actual exact width and height on the SVG root element, such as 800px by 600px, might speed things up rather than a relative one, such as 100%. I could see the browser needing to do extra work with sizing + layout. I wonder what would happen too if you nested the SVG root into a DIV that had absolute position; perhaps this would also help the browser do less work and not try to do ‘layout’ on the HTML container? It might be interesting to see what the frame rate is of your demo when everything is inside of a .svg file in a pure SVG context for Firefox.

    Also, I notice the #framerate container is on top of the SVG; what happens if this is moved off and perhaps is just a window.console.log operation? I wonder if the browser having to blit an HTML layer on top of the SVG slows things down for Firefox.

    You could also try doing things in an HTML rather than XHTML context, dynamically creating your SVG root using createElementNS which will work in HTML. Perhaps the XHTML rendering pipeline isn’t as optimized in Firefox.

    Best,
    Brad

  10. Jeff says:

    Hi Brad,

    I tried to only do things that wouldn’t affect the visual results of the original demo – thus changing width=”100%” to a static value would be “cheating” 🙂

    But I’m sure changing the width/height values would definitely affect the performance in a positive way.

    I did try a HTML5 version of the demo with no discernable difference in performance in any browser.

    Thanks,
    Jeff

  11. You could also try the following, which in my quick tests it gave ~1-2fps more in Opera:

    – add a rect that covers the entire canvas
    – before the circles are recreated/moved toggle the color of the rect, or change it such that it’s not really visible either way

    Basically this is a hint to say “hey, I’m going to change everything”. I guess you could do the same by setting ‘viewport-fill’ on the svg root.

    It would also be interesting to get the results for all the browsers when running in Windows (for comparison).

  12. Jeff says:

    Thanks Erik for the suggestion. Can you give any insight into Opera’s implementation of suspendRedraw()/unsuspendRedraw() ?

  13. suspend/unsuspendRedraw in Opera, well, basically I wouldn’t count on that for any dramatic differences in 10.50 and later since we are changed when we update the rendering compared to previous versions. Was there something in particular you wanted to know?

    http://dbaron.org/log/20100309-faster-timeouts would also be interesting to test with, replacing setInterval.

    For the suggestion, I don’t think it would make that much difference if the elements aren’t reused (the setup cost would otherwise probably erase any performance gain). Worth trying, if you hold on to the elements.

    In general this (the original) benchmark test is quite biased towards write-once immidiate-mode graphics. You get the results you ask for.

  14. Bela Lubkin says:

    Hi —

    Ran across this while random walking about blogs, probably won’t return, but anyway: did you try observing the actual behavior, not just the FPS counts?

    As I write I’m using Opera 10.10 (old) on Ubuntu 9.10 x64. Each of the three versions you presented gave different results. According to the FPS counters, original was ~6, suspend was ~8, offscreen render was back to ~6.

    But visually — suspend was noticably slower than either, and chunky; offscreen render was noticably faster than the other two.

    Which makes me suspicious of how the FPS is being calculated. I didn’t look into the implementation, have no idea whether it’s your bug, odd behavior of this Opera build, or me mis-perceiving the frame rates.

    BTW, results on the same machine, but in an Windows XP SP3 (32-bit) VM running Opera 10.52 build 3337 (current snapshot) under VMware Workstation 7.0.1: 16, 13, 22. Much smoother too.

    Suggestion. This would violate the “keep it the same as the original” constraint, but, add a single ball in a different color and which always moves according to the same motion vector in every run (e.g. straight to the right @unit speed). This would make it much easier to confirm one’s perception of visual speed.

    >Bela<

    PS: please ping me out of band if you write a substantive response. The intertubes know of my email address. Include URL or enough cookie crumbs for me to google this page again ;-}

  15. Bela Lubkin says:

    Funky. After posting that, Opera complained that the page wasn’t parsable as XML due to the double-dash I included; should it reparse as HTML? Not sure if that means the site is mis-mime-stamping the page as text/xml, it’s doing so correctly but failing to escape the double-dash, or an Opera bug. Whee…

    Opera’s message:

    XML parsing failed: syntax error (Line: 548, Character: 140)

    Reparse document as HTML [[[ a button ]]]

    Error:
    invalid comment (containing ‘- -‘ that is not followed by ‘>’) [[[ I added the space ]]]

    Specification:
    http://www.w3.org/TR/REC-xml/

  16. Bela Lubkin says:

    Bizarre. Google Chrome (5.0.342.3 dev) gives a different error:

    This page contains the following errors:

    error on line 569 at column 141: Comment not terminated
    Recent comments

    Bela Lubkin:
    Below is a rendering of the page up to the first error.

    [[[ again, I added the space in the double-dash ]]]

    The message is followed by what appears to be a complete rendering of the page. Go figure.

  17. Bela Lubkin says:

    Meh. Chrome’s error message had a bunch of literal tags in it, which got rendered out of existence in the resulting display. I will now stop abusing your blog to experiment with browser parsing bugs.

  18. Jeff says:

    Hi Bela – I don’t think I have a substantive response, but I do want to apologize for the grief my blog seems to have caused you! (I don’t see any errors in Firefox, Chrome or Opera here)

  19. Shaun says:

    This made for a very interesting read. I have been messing around with different performance issues with SVG myself recently. This is largely due to the fact that I am attempting to provide a snappy user experience while displaying 20000+ elements. The suspendRedraw() and unsuspendRedraw() are definitely something I am going to have to keep my eyes on in the future

  20. Patrick Dengler says:

    Hey Jeff, great stuff.

    Try using this not on a VM so you can take advantage of hardware acceleration.

    Patrick

  21. HRJ says:

    Hi,

    Thanks for this detailed post.

    It is really unfortunate that suspendRedraw is not well implemented in browsers. Otherwise, my guess is SVG animations could be quite fast.

    Hope there is more interest generated in SVG now that ie9 is supporting SVGs natively.