{"id":757,"date":"2010-03-22T01:07:09","date_gmt":"2010-03-22T01:07:09","guid":{"rendered":"http:\/\/www.codedread.com\/blog\/?p=757"},"modified":"2011-11-07T17:16:58","modified_gmt":"2011-11-07T17:16:58","slug":"know-the-beast","status":"publish","type":"post","link":"https:\/\/www.codedread.com\/blog\/archives\/2010\/03\/22\/know-the-beast\/","title":{"rendered":"Know The Beast"},"content":{"rendered":"<p><object type=\"image\/svg+xml\" width=\"100\" height=\"100\" style=\"float:right\" data=\"http:\/\/codedread.com\/clipart\/tools.svgz\">[clipart]<\/object><a href=\"http:\/\/www.themaninblue.com\/writing\/perspective\/2010\/03\/22\/\">Cameron Adams<\/a> 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.<\/p>\n<p><strong>Update: Added some browser scores for Nov 2011<\/strong><\/p>\n<p><!--more--><\/p>\n<h3>Particle Creation<\/h3>\n<p>Cameron initializes the particles this way:<\/p>\n<pre>\r\n\tvar domNode = document.createElementNS(SVG_NS, \"circle\");\r\n\tvar benchmark = document.getElementById(\"benchmark\");\r\n\tbenchmark.appendChild(domNode);\r\n\t\r\n\t\/\/ Set initial position to middle of screen\r\n\tdomNode.setAttribute(\"cx\", x + PARTICLE_RADIUS);\r\n\tdomNode.setAttribute(\"cy\", y + PARTICLE_RADIUS);\r\n\tdomNode.setAttribute(\"r\", PARTICLE_RADIUS);\r\n\t\r\n\t\/\/ Set colour of element\r\n\tdomNode.setAttribute(\"fill\", COLORS[Math.floor(Math.random() * COLORS.length)]);\r\n<\/pre>\n<p>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).<\/p>\n<p>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.<\/p>\n<h3>DOM Animation - the Naive Approach<\/h3>\n<p>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:<\/p>\n<pre>\r\n\t\t\/\/ lots of other JS code here\r\n\t\tdomNode.setAttribute(\"cx\", nextX + PARTICLE_RADIUS);\r\n\t\tdomNode.setAttribute(\"cy\", nextY + PARTICLE_RADIUS);\r\n<\/pre>\n<p>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.<\/p>\n<ul>\n<li><a href=\"http:\/\/codedread.com\/browser-tests\/particle\/particle.xhtml\">Click here to try the original demo<\/a><\/li>\n<\/ul>\n<table>\n<thead>Original Results<\/thead>\n<tr>\n<th>Browser<\/th>\n<th>Frame Rate<\/th>\n<\/tr>\n<tr>\n<td>Firefox 3.6 (OSX)<\/td>\n<td>1.75 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 3.7 Nightly (OSX)<\/td>\n<td>1.9 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 8 Beta (OSX)<\/td>\n<td><strong>3.8 fps<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Opera 10.10 (OSX)<\/td>\n<td>12.5 fps<\/td>\n<\/tr>\n<tr>\n<td>Opera 10.50 (OSX)<\/td>\n<td>14.5 fps<\/td>\n<\/tr>\n<tr>\n<td>IE9 Preview 1 (Win7 VM)<\/td>\n<td>28 fps<\/td>\n<\/tr>\n<tr>\n<td>Safari 4.0.5 (OSX)<\/td>\n<td>32 fps<\/td>\n<\/tr>\n<tr>\n<td>Chrome 5 Nightly (OSX)<\/td>\n<td>40 fps<\/td>\n<\/tr>\n<tr>\n<td>Chromium 17 Nightly (OSX)<\/td>\n<td><strong>55 fps<\/strong><\/td>\n<\/tr>\n<\/table>\n<h3 id=\"suspendredraw\">Suspension<\/h3>\n<p>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 <em>did<\/em> have an effect on Firefox's frame rate.  <\/p>\n<p>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:<\/p>\n<pre>\r\nunsigned SVGSVGElement::suspendRedraw(unsigned \/* maxWaitMilliseconds *\/)\r\n{\r\n    \/\/ FIXME: Implement me (see bug 11275)\r\n    return 0;\r\n}\r\n<\/pre>\n<p>Maybe WebKit will one day implement this and the technique will become more broadly useful. <strong>[Update Nov 2011: This has been implemented in WebKit]<\/strong><\/p>\n<ul>\n<li><a href=\"http:\/\/codedread.com\/browser-tests\/particle\/particle-suspend.xhtml\">Click here to try the demo that uses suspendRedraw<\/a><\/li>\n<\/ul>\n<table>\n<thead>SuspendRedraw Results<\/thead>\n<tr>\n<th>Browser<\/th>\n<th>Frame Rate<\/th>\n<\/tr>\n<tr>\n<td>Firefox 3.6 (OSX)<\/td>\n<td>3.2 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 3.7 Nightly (OSX)<\/td>\n<td>3.4 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 8 Beta (OSX)<\/td>\n<td><strong>8 fps<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Opera 10.10 (OSX)<\/td>\n<td>8.2 fps<\/td>\n<\/tr>\n<tr>\n<td>Opera 10.50 (OSX)<\/td>\n<td>23.5 fps<\/td>\n<\/tr>\n<tr>\n<td>IE9 Preview 1 (Win7 VM)<\/td>\n<td>N\/A<\/td>\n<\/tr>\n<tr>\n<td>Safari 4.0.5 (OSX)<\/td>\n<td>32 fps<\/td>\n<\/tr>\n<tr>\n<td>Chrome 5 Nightly (OSX)<\/td>\n<td>40 fps<\/td>\n<\/tr>\n<tr>\n<td>Chromium 17 Nightly (OSX)<\/td>\n<td><strong>45 fps<\/strong><\/td>\n<\/tr>\n<\/table>\n<p>NOTE: IE9 Preview 1 has not implemented suspendRedraw() and the demo does not run.<\/p>\n<h3 id=\"manual-offscreen\">Manually Doing Off-Screen Rendering<\/h3>\n<p>I then went and did something crazy.  <\/p>\n<p>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 &#60;g&#62; from the DOM, call all the particle draw() functions, then re-add the &#60;g&#62; to the DOM.  <\/p>\n<p>This had a <em>dramatic<\/em> 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.<\/p>\n<ul>\n<li><a href=\"http:\/\/codedread.com\/browser-tests\/particle\/particle-g.xhtml\">Click here to try the demo with manual off-screen rendering<\/a><\/li>\n<\/ul>\n<table>\n<thead>Manual Offscreening Results<\/thead>\n<tr>\n<th>Browser<\/th>\n<th>Frame Rate<\/th>\n<\/tr>\n<tr>\n<td>Safari 4.0.5 (OSX)<\/td>\n<td>12.75 fps<\/td>\n<\/tr>\n<tr>\n<td>Opera 10.10 (OSX)<\/td>\n<td>14 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 3.6 (OSX)<\/td>\n<td>21.5 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 3.7 Nightly (OSX)<\/td>\n<td>21.5 fps<\/td>\n<\/tr>\n<tr>\n<td>Opera 10.50 (OSX)<\/td>\n<td>23.5 fps<\/td>\n<\/tr>\n<tr>\n<td>IE9 Preview 1 (Win7 VM)<\/td>\n<td>25 fps<\/td>\n<\/tr>\n<tr>\n<td>Chrome 5 Nightly (OSX)<\/td>\n<td>26.5 fps<\/td>\n<\/tr>\n<tr>\n<td>Firefox 8 Beta (OSX)<\/td>\n<td><strong>37 fps<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Chromium 17 Nightly (OSX)<\/td>\n<td><strong>49 fps<\/strong><\/td>\n<\/tr>\n<\/table>\n<h3>Conclusion<\/h3>\n<p>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.<\/p>\n<p>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 &#60;canvas&#62; element.  Let's hope IE9 also implements a GPU-accelerated version of this element too!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>[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 [&#8230;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25,46,11,14,28],"tags":[56,172,91,198],"class_list":["post-757","post","type-post","status-publish","format-standard","hentry","category-software","category-svg","category-technology","category-tips","category-web","tag-animation","tag-canvas","tag-graphics","tag-svg"],"_links":{"self":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts\/757","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/comments?post=757"}],"version-history":[{"count":24,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts\/757\/revisions"}],"predecessor-version":[{"id":768,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts\/757\/revisions\/768"}],"wp:attachment":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/media?parent=757"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/categories?post=757"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/tags?post=757"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}