| By Dietrich Kappe | Article Rating: |
|
| May 4, 2007 11:00 AM EDT | Reads: |
6,546 |
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.
JavaScript Profiling
In some cases you may not know precisely where a performance bottleneck lies. Using our technique of surrounding suspected bottlenecks with timestamps won't work in these cases. We need a way to get a broader overview of application performance. Fortunately there are a number of profiling tools. Unfortunately, they're not standard across browsers.
One of the best tools is the Venkman JavaScript Debugger from Mozilla. It's available at www.mozilla.org/projects/venkman/. Venkman has a profiling option that lets you capture profiling data, then save it and analyze where your application spent its time. Simply select the menu Profile->Collect Profile Data and use your application. Then, when you're finished gathering your profiling data, select the menu Profile->Collect Profile Data to stop collecting data, then Profile->Save Profile Data to finish. You can also click the clock icon to turn profiling on or off. When profiling is enabled, the clock icon will have a green check mark on it. To restart profiling, select the menu Profile->Clear Profile Data.
When profiling is enabled in Venkman, it will collect the number of calls and the maximum, minimum, and average call times for every function called in your application. If you want to restrict profiling, clear or save profile data to particular JavaScript files, you simply select those scripts or functions in the Loaded Script area before selecting the appropriate Profile menu item.
The supported data formats for saving are HTML, Text, CSV, and XML. Specify the format with the file extension of the saved file. Figure 9.2 shows the saved profile data in HTML form (I've used the dot notation lookup performance test as an example):
The profile output may look cryptic at first, but once you get the hang of it, you can track down hotspots in short order. Specifically, for each function profiled, look at the number of calls and the total time in milliseconds spent in each function. Find the big culprits and drill down into where those functions are spending their time - they may just be wrappers for other more wasteful functions. Ignore the small stuff. There's no need to optimize functions that are hardly ever called and take up no CPU time.
In IE, your best bet for profiling is the commercial Tito Web Studio JavaScript Profiler (www.titosoftware.com/profiler.php). It provides functionality similar to Venkman.
Memory Optimization
Until recently, we didn't really concern ourselves with memory consumption and garbage collection in JavaScript. In traditional Web applications, each page load wipes the JavaScript memory slate clean. If there were memory leaks, usually that was someone else's problem - the poor Web developer whose page loaded at the end of a long day of browsing and consequently ran slow as molasses. Users typically knew enough to exit and restart their browsers in any case, starting fresh without any memory leaks.
Because of the clean memory slate that came with each page load, memory management and garbage collection is one of the least stress-tested areas of browsers - in short, a weak link. With AJAX, more pages will stay in memory longer, and some single page applications will stay in memory for hours or even days, putting the spotlight squarely on this weakness. This fundamental change in how Web applications make use of the browser means we have to pay special attention to memory management issues, reducing unnecessary allocation of objects and avoiding memory leaks.
One of the more common ways of leaking memory is through accidental closure. What is a closure? Without getting into too much detail, when you execute a function, it creates an "execution context," where all of the local variables and other objects created or accessed during the execution of that function reside or are referenced.
function example() {
// num exists in the execution context of example()
var num = 5;
}
Each execution of the function creates a new context. When the function returns, all of the memory used up by that execution context is reclaimed. The only time that the memory isn't reclaimed is if a reference to a function created in that execution context becomes accessible outside of the function.
function example2() {
// num exists in the execution context of example()
var num = 5;
function innerFunction() {
num = num + 5;
return num;
}
return innerFunction;
}
var funcRef = example2();
In the example above, the inner function has to access the variable num in that invocation of example2's execution context. That means that num, and any other memory resident objects created in the execution context, stays in memory until the reference to the inner function is garbage collected. This behavior is part of the specification of ECMAScript and can be quite powerful when used properly, but can cause all manner of memory leaks when used improperly. It's actually quite easy to create an accidental closure.
The best way to address the problem of these memory leaks is to be vigilant when returning functions from within a function call. That happens quite a bit if you're assigning anonymous event handlers to a DOM object inside of a function call. Just be aware that any variable declared in or available as a parameter in the surrounding function will live on as long as the inner function is around, so don't declare huge arrays or objects needlessly.
A more serious type of memory leak in IE comes from assigning event handlers to a DOM object (or any COM object, for that matter). It's a sure way to leak memory permanently in IE. Take for example the following function that inserts a clickable div into the page:
function insertClickableDiv() {
var elem = document.createElement("div");
elem.innerHTML = "click me";
elem.onclick = function() { elem.className = "boldDiv"; };
document.body.appendChild(elem);
}
This creates a closure, since we assign a function created inside the outer function to a DOM element outside of its scope. Further, the DOM element, which is a part of the closure, refers to the anonymous inner function, creating a circular reference. By circular reference, I mean that two objects refer to one another. In this case, the function's execution context refers to the DOM element and the DOM element refers to the function via its onclick event handler.
Normally, good automatic garbage collection algorithms will handle circular references, but in IE, COM objects are reference-counted. That means that the browser keeps track of how many references there are to an object. In the case of a circular reference, none of the participating objects ever have a reference count of zero, so they stay in memory for good even after a page load.
There is a tool for IE called Drip that detects these sorts of memory leaks (http://outofhanwell.com/ieleak/index.php?title=Main_Page). To track down memory leaks, start up the Drip browser and load the application you want to test, exercise its functions, then click the "Show DOM Leaks" button. You'll see a list of the leaked DOM elements.
To track down where your leaks are in a large body of code, you can add an ID attribute to each DOM element, perhaps using the name of the enclosing function or method as a root. If you see lots of DOM elements whose IDs are all of the form "insertClickableDiv_123", then you'll know where to start looking.
Once you've identified the source of the problem, you can fix it by making sure the event handler function isn't in the execution context of the created DOM element. One way to do this is to use external functions. A better way is to wrap the DOM element creation into its own execution context one level down:
function insertClickableDiv_NoLeak(i) {
var changeToBold = function() { this.className = "boldDiv"; };
(function(){
var elem = document.createElement("div");
elem.innerHTML = "click me";
elem.onclick = changeToBold;
document.body.appendChild(elem);
})();
};
The changeToBold function is external to the anonymous function that we create and call all in one step. Since there's now no reference from the execution context of the event handler, we've broken the circular reference and things should be okay.
Object Pooling
Memory leaks can make an application sluggish, but heavy memory consumption, even in the absence of leaks, can plague complex object-oriented applications. That's true of Java and C#, not just JavaScript. One approach to handling the creation of expensive resources is pooling. You see this quite a bit in applications with database connections, which are expensive to create. The basic idea is that you have a pool of reusable resources ready to go. When an application asks for a resource from the pooling subsystem, an instance is pulled from the pool and returned. When the application
is done with the resource, it returns it to the subsystem and it's put back in the pool. If we run out of available instances, we can do any number of things: we can force the application to wait until an instance becomes available or we can create an instance on-the-fly. The management of resource pools is a subject unto itself. Look at the implementation of Apache server pools and JDBC connection pools for some examples of how this management is done.
We can apply this pooling principle to any run-of-the-mill JavaScript object, but it makes the most sense to apply it to our most expensive resources. The XMLHttpRequest object can be pretty expensive. And if we make hundreds or thousands of requests over the lifetime of an application, it makes sense to reuse them. The key, of course, is the term reusable. The XMLHttpRequest can have some reusability problems in IE, if you don't use it correctly. The trick is in the order you issue the open() method call and assign the onreadystatechange listener.
// reusable in all browsers except IE
xhr.onreadystatechange = function() {
// process result
};
xhr.open("GET", url, true);
xhr.send("");
// reusable in all browsers including IE
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
// process result
};
xhr.send("");
Always call the open() method first, then assign the event listener. Once we have this little trick under our belts, we can create our XHR pool.
Listing 9.2
var XHRPool = (function(){
// static private member
var stack = new Array();
var poolSize = 10;
var nullFunction = function() {}; // for nuking the onreadystatechange
// private static methods
function createXHR() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject('Microsoft.XMLHTTP')
}
}
// cache a few for use
for (var i = 0; i < poolSize; i++) {
stack.push(createXHR());
}
// shared instance methods
return ({
release:function(xhr){
xhr.onreadystatechange = nullFunction;
stack.push(xhr);
},
getInstance:function(){
if (stack.length < 1) {
return createXHR();
} else {
return stack.pop();
}
},
toString:function(){
return "stack size = " + stack.length;
}
});
})();
Note that this implementation depends on good programmer behavior. If you don't release the resource in the end, it will never make it back into the pool. Use of the pool is straightforward. You just have to remember to call XHRPool.release() in the onreadystatechange handler.
function testXHRPool(url) {
var xhr = XHRPool.getInstance();
// do the operation
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState==4) {
// if "OK"
if (xhr.status==200) {
// process result
}
XHRPool.release(xhr);
}
};
xhr.send("");
}
Using our timing function from before to create 100 XMLHttpRequest objects 500 times, this change resulted in more than a 50% speed improvement over a straight XMLHttpRequest creation in Firefox, Opera, and Safari, and a more than a 95% improvement in IE6.
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.
Published May 4, 2007 Reads 6,546
Copyright © 2007 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Dietrich Kappe
Dietrich Kappe is a co-founder and the CTO of Pathfinder Development, a firm that combines User Experience Design and Agile to speed software product development. He published one of the first 100 public websites and launched one of the first Java Servlet-based web applications. He has been a software engineer for two decades, a frequent open source contributor, and has developed applications for the Media, Financial Services, Insurance and Healthcare industries.
- Kindle 2 vs Nook
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Adobe’s Aiming ColdFusion at Multiple Clouds
- Windows 7 – Microsoft’s First Step to the Cloud
- Ulitzer Provides a Powerful Social Journalism Platform
- Jill Tummler Singer, Deputy CIO of CIA, Keynotes at GovIT Expo
- Open Source Mobile Cloud Sync and Push Email
- Kindle 2 vs Nook
- The Difference Between Web Hosting and Cloud Computing
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- US Post Office Hops a Ride on NetSuite’s Cloud
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Adobe’s Aiming ColdFusion at Multiple Clouds
- Building a Drag-and-Drop Shopping Cart with AJAX
- What Is AJAX?
- Google Maps! AJAX-Style Web Development Using ASP.NET
- Flashback to January 2006: Exclusive SYS-CON.TV Interviews on "OpenAjax Alliance" Announcement
- AJAXWorld Conference & Expo to Take Place October 2-4, 2006, at the Santa Clara Convention Center, California
- AJAX Sponsor Webcasts Are Now Available at AJAXWorld Website
- How and Why AJAX, Not Java, Became the Favored Technology for Rich Internet Applications
- "Real-World AJAX" One-Day Seminar Arrives in Silicon Valley
- AJAXWorld University Announces AJAX Developer Bootcamp
- AJAX Support In JadeLiquid WebRenderer v3.1
- Where Are RIA Technologies Headed in 2008?
- Struts Validations Framework Using AJAX



































