Your Performance Problem May Not Be What You Think
by Greg
Performance problems can be hard to deduce, and the root cause is often not very obvious. On a recent project, I ran in to a serious performance problem rendering part of a DOM tree. Those things that were suggested as the probable causes, turned out not to be. The root cause turned out to be not so obvious, and was only found through deeper analysis using good debugging and profiling tools, in this case, Firefox and Firebug.
The site displays a map. The map has a bunch of dots, each of which represents a range of votes received from a certain locale, in this case a city. Each dot is made up of a div element, which contains an img element: the dot itself. These elements are all added to the DOM dynamically, via JavaScript, based on the results of a JSON-formatted string returned from an AJAX request.
One final snag: The images [i.e. the dots] might change over time, ad we don’t want changes to the images to require changes to the code, so we need to read the width and height from the images when the page loads.
This worked well for small data sets. I generated a large volume of test data, to see how the map would look as the voting got heavy. To my chagrin, the rendering time for the map grew very long, very quickly. Clearly, we had a major malfunction.
The “obvious” reasons for the long rendering seemed to be:
1- The div elements’ attributes for style.position, style.visibility, style.top, style.left, style.zIndex are set in two ways, to “support both IE 7 and Firefox”. Surely this must be what’s taking so long!
Wrong
2- The div’s and img’s are being rendered by concatenating a string made up of the resulting html, and applying it to the DOM via the innerHTML attribute of a parent element. This must be slower than generating the DOM more “elegantly”, by building it up with calls to document.createElement() and <element>.appendChild(), right?
Wrong, again.
3- Perhaps the problem is caused by reading properties of an image in the page to get the width and height, and that take a bit of time?
Actually, this is where the problem lies, but only for a specific part, and we need to do this to avoid fragile code, so we can’t drop this completely. As it turns out we don’t have to.
To find the problem, using Firefox and Firebug, I did some profiling of the code, while changing certain aspects, and noting the results.
NOTE to all: These rendering times are for a sample set with over 100000 votes in the database, so we are talking about a lot of div and img elements here.
NOTE to the Architecture Astronauts: The methodology of this design is not open for debate. Suffice it to say, it was the best solution, given other constrains on the project.
Here are some sample page render times, before making any changes:
Firefox: 45 s, 46 s, 46 s, 46 s, 46 s
IE and Safari: 6 s, 6 s, 6 s, 10 s, 6 s, 7 s
Interesting. Obviously, the problem is in Firefox, though better numbers for IE and Safari are desirable.
Next, I tried commenting out the code for the IE-specific styling. Not a solution, since IE support is a must, but just for completeness:
Firefox: 44 s. 48 s, 45 s, 44 s, 45 s
IE and Safari: 5 s, 6 s, 6 s, 5 s, 6 s
Not much difference there, so that’s apparently NOT the problem. Interestingly, perhaps not surprisingly, commenting out the Firefox version of the styling also broke Safari.
Next, I re-wrote the element code to use createElement() and appendChild():
Firefox: 46 s, 46 s, 46 s, 47 s, 45 s
As noted in the summary, above, this did squat.
To home in on the specific lines of code that were running long, I added calls to Firebug’s console.time(), and console.timeEnd() functions throughout the code processing the AJAX response. This slowed things down even further, but reviewing the output for stuff that was orders of magnitude larger than the rest yielded these lines as the culprits:
var dotSize = document.getElementById(“hiddenDot”);
pointLat = pointLat - ( [dotSize.height] / 2 );
pointLong = pointLong – ( [dotSize.width] / 2);
This code was being executed in every iteration of the loop that processes the AJAX response, and the execution of these lines was hugely longer than every other part of the loop. Narrowing it further, and without digging too deep into what I went through to figure it out, the actual operation that was taking almost all of the time in this was the query to the “hiddenDot” element to determine the height [dotSize.height]. The call to getElementById was fast. The query for width was insignificant. So were the divisions, even when I used an image with an odd-sized width or height, which would require floating point math. Setting the width and height attributes on the “hiddenDot” img element [this had not been done prior to my looking at it] made no difference.
In the end, given time constraints that prevented me from researching this any further than a couple of additional Google searches, I never found any clues to why requesting the height attribute on an img element in Firefox takes so long. The solution to the performance problem was to move the code for getting the width and height out of the loop, which is safe since the img would not change for the duration of a page load.
This sped things up hugely, bringing Firefox rendering times in line with those of IE and Safari.
You can argue that the code that caused the problem should never have been within the iteration in the first place, but it’s also reasonable to say that no one would ever have thought that particular operation would consume so much time. Everyone that I discussed this with, a) thought the problem would have been in the extra IE code, the innerHTML use, or some other area, and b) were as surprised as I was at the culprit.
Experience helps when optimizing systems for performance, but there are those cases when the only way to find the solution is with scientific analysis using the right tools for the job.