Error. Page cannot be displayed. Please contact your service provider for more details. (3)
Link of the day - Free Macbook Air
How to Profile and Optimize Ajax Applications
I've been asked by a few folks recently on how to profile an Ajax application and improve its performance, so I thought I would detail some of the specific and general strategies to do this in a tutorial here.
Premature Optimization is the Root of All Evil...
The first thing to know about profiling happens far, far before you do an optimization session, at the beginning of your project; it is one of the most challenging phases because it requires you to balance two different approaches that people struggle with.
The first rule is to avoid premature optimization at all costs. I have literally been at companies that I've seen destroyed by premature optimizations. I can't name names, but just know that doing premature optimization can massively destroy your design and create such a complicated system that when you actually come to your performance and profiling phase, you don't know where to start. The most important thing is to build the right system. Don't take short cuts based on what you believe will affect its performance; build a system that is maintainable, that satisfies getting your functionality out the door, that satisfies getting your system built quickly, whatever your important things are, but don't do premature optimization.
...But Order of Magnitude is Important
On the other hand, it's very important to do what I call Order of Magnitude calculation, also known as Big O in more formal computer science. These are decisions that you need to make that will affect the performance in an order of magnitude way. When I say order of magnitude, it means the difference between 100 milliseconds and 1,000 milliseconds, or a couple of seconds and 10, 20, 30 seconds; these are tricky and you should make these decisions early on.
In general, what I do, is I'll do a simple prototype that exhibits the major characteristic of several different architectures. I'll see how these prototypes perform in an order of magnitude way and build these before I decide on my architecture. Some of this is just intuition and experience, but for any area of the system that you think might cause performance problems or is high risk for performance issues should have a simple prototype before. You shouldn't go crazy about this.
One of the major decisions I had to make for the HyperScope project was the kind of architecture I was going to use. One of my options was to create a sort of DOM walker architecture where I implemented all of Engelbart's functionality by walking over a DOM tree. My other alternative was to use XSLT on the client side to render an XML data structure according to the addressing and viewspec options in force, then blit the HTML results to the screen. The third alternative was to somehow use HTML in conjunction with XSLT and XPath. Each of these architectures have their strengths and their weaknesses, but I wanted to understand their order of magnitude performance characteristics before I chose one. I prototyped all three, and I found that the DOM walker architecture suffered from one or two orders of magnitude worse performance than the other two architectures. I ended up settling on the XSLT architecture both because it was faster, handled some important edge conditions, and worked across Internet Explorer better than the HTML + XSLT/XPath architecture.
Scalability vs. Performance
A quick note. Many programmers confuse scalability with performance. Scalability has to do with how your performance changes as you add more of something, such as more users. Your performance can be very low, but you can have linear scalability, which people confuse. For example, Enterprise JavaBeans promised linear scalability; what they didn't say was the overall performance actually was pretty lousy to start with in most cases. It's important to separate these two terms. This entire discussion is about end-user performance, not about scalability or how an application changes as you add users, that's a whole other topic.
The Profiling Infrastructure, or, The Scientific Mind
After you've designed your architecture and built your system, you might find that early builds of your system are slow. At a certain point, you can decide to continue building all of your functionality or you can decide to do a profiling session.
At this point, the most important thing is to approach the task like a scientist. You want to approach this with a fresh mind. Like a scientist, you need to collect data. You need to create a profiling infrastructure.
So your first step is creating a profiling infrastructure which basically means you need a way to calculate your total page load time and the time it takes to run important functions. This should include fetching all of your resources, executing all of your page load functionality, doing your actions, and you should also have a way to capture the amount of time for individual segments. It's important to differentiate trying to optimize page load time from trying to optimize some operation after you are already loaded; you should separate these two things.
In HyperScope, since I use the Dojo project, I use the dojo.profile library. The one tricky thing is that dojo.profile can't capture the total amount of page load time, because obviously it can't start up before Dojo is loaded. What I had to do, if you crack open Hyperscope, you will see at the very, very top before everything else that there is a script block that captures the initial start time, with a new Date().getTime() call. I save this value on the window object as a variable, such as window.docStartTime, and then later on after I've finished everything, I can calculate the end time and now I've got the total document time.
Something else that is important is that you want to have a way to toggle your profiling infrastructure on and off without editing your source code. Again in HyperScope, I put a URL flag that you can put up in the browser location bar, such as ?profiling=true. If that is on, then I will dump out the profiling . This is important, because you need to be able to switch back and forth between profiling without editing your code, especially on a production server where you want to see how the network and its particular characteristics affect performance.
Now that you've got a profiling infrastructure you can start collecting data like a scientist.
There are a couple of important things about collecting data. The first thing you need to know is you should always prime the pump. That's basically saying you should always take many readings of your data, such as five or ten readings, and you should throw the first three away. This is called priming the pump because you never know how different parts of the infrastructure may affect initial performance; for example, if you are hitting a SQL database, the first call might cache the SQL results, so subsequent calls are faster, or some of the information from the disk might stay in memory in a memory cache.
Browser Caches, Local Proxies, and Remote Servers
Once you've primed the pump, you need to differentiate two things in your test runs. You should see what the time is where you clear the browser cache each time. This simulates hitting a site for the very first time. You should also get readings for pulling from the browser cash. You'll get two different numbers, and both are important.
The other thing that you should do is you should both get readings for a local server, where you're running a local web server on your machine, so you can see the characteristics without the network getting in the way. You should also put it up on a remote web server on a real network and then see how that affects performance, because network latency will kill you. The Internet has terrible latency in general, even if the bandwidth of a particular connection is fast. Make sure the web server locally and remotely are the same one; if production is Apache, make it Apache local, not Jetty or something else. Also try to simulate the same configuration options.
Finding Bottlenecks, or Hunt the Wumpus
Once you've got your data, in general what you'll find is you'll have one, two, or three bottlenecks that consume 30 to 60 percent of the total time. You'll start by attacking these bottlenecks.
Every time you attack a bottleneck, you will experiment and try some proposed solution; you'll turn your profiling on and find that, for example, if you used a different kind of DOM method, or a different strategy, that you'll actually get better or worse performance. The most important thing is to attack the low-hanging fruit, the bottlenecks, and don't get lost in small meaningless performance gains. Optimizing creates complexity in general, so only sacrifice simplicity if it means a good performance gain. At a certain point, you'll find that there is a law of diminishing returns, where you'll have good enough start-up performance and good enough application performance, and it won't be worth adding more complexity.
Specific Ajax Performance Tips
Now that you know some general principles about profiling and about setting these things up, I want to share some specific things that tend to affect Ajax performance, especially start-up time.
All of this together, massively, massively helps load time. I have found that this bottleneck affects other apps as well; it's the best place to start. At Rojo, I found very similar characteristics and created a similar system. So, this is something that you'll want to do.
Finally, if you are generating dynamic HTML from XSLT or being pulled from a remote web server, and then inserting this into your document in one giant chunk, keep in mind that the size of this HTML will greatly affect the performance of putting it into the document. Cleaning up this HTML and making it much smaller will affect performance. In HyperScope, I used to write out inline event handlers in the HTML I generated from my client-side XSLT, such as onmouseover, onmouseout, etc., on each row. Moving these out and just creating a single event handler on the HTML's container that catches these events majorly improved the performance because the size of the HTML was reduced overall.
Knowing When to Stop
All of this work can pay off. For the HyperScope project, I was able to create an actual performance improvement of about 70%. In addition, perceived performance improved, as the user saw a document much quicker even though parts of the document were still being blitted to the screen in the background.
Knowing when to stop is the important, final point. With HyperScope, there were more bottlenecks that I could have attacked. However, we only have a few weeks of development left, and there is still more important functionality and QA that needs to be addressed. Once you've brought performance to a level that is solid, you should evaluate how this new performance fits into your timeline, other features, etc. Once you've hit your other important milestones you can return to profiling if it is needed and you have time.