IE+JScript Performance Recommendations Part 3: JavaScript Code Inefficiencies

Hello again, this is Peter Gurevich, Performance PM for IE. We have gotten a lot of good feedback from our first posts on IE + JavaScript Performance Recommendations Part 1 and Part 2, so I am eager to hear what you think of our third installment.

JScript Code Inefficiencies – Chapter 2

Here we’ll be concentrating on specific inefficiencies related to closures and object oriented programming.

Avoid Closures if Possible

Closures (functions that refer to free variables in their lexical context) are both extremely powerful and extremely dangerous. There are certain programming techniques that demand their use and once you learn how to use them they can quickly become one of the most overused features of the JScript language. Currently the misuse of closures in connection with the IE DOM and various COM components are the number one cause of heightened memory usage within IE. Also, while they are useful, this is a performance article and there are not really any scenarios where a closure is going to provide more performance throughput than a bare function or inline code.

Closures are most often used when attaching event handlers. The purpose in this case is to encapsulate some of the scope for the currently running function into a brand new function that can later be used when the event handler is invoked. The trouble with this approach is that circular references between the scoped variables and the closure are nearly invisible to the eye. The extra memory pressure of leaving these objects around means extra garbage collection pressure, possibly extra work for IE, or more bookkeeping for other hosts. APIs for handling the retrieval of remote data are useful as an example.

function startDownload(url)
{
var source = new ActiveXObject(“Ficitious.UrlFetcher”);
source.ondataready = new function() { source.ondataready = null; }
source.startRequest(url);
}

The above example is pretty basic. The script doesn't do any real work, but sets up a mock scenario of a closure. What happens when the ondataready event never fires? We expect it to, but it might not. If it doesn't fire, the closure in this case has a reference to the source object and the source object back to the closure. This is an implicit circular reference, hard to spot, and IE leaks memory. This is never good for performance. Furthermore, every time the startDownload function is called, it means allocating a new object to store all of this state. Rewriting the sample to not use closures is very specific to your application. If you only allow a single instance of the object to exist, then you could take advantage of a global singleton. If you allow multiple requests, then a pooling mechanism is probably more appropriate along with a method for dispatching and handling all incoming requests even if a particular request is signaling it is ready. For additional information on closures, please see the Closures section in Understanding and Solving Internet Explorer Leak Patterns.

Don’t use Property Accessor Functions

A common technique in object oriented programming is to use property accessor functions. An example would be in the form of [get/set]_PropertyName (or many others depending on the style). The basic premise is a local member variable for some class followed by two methods for either retrieving or setting the property. These methods are often used for controlling member visibility, but in JScript everything is visible. Further, most object oriented languages optimize the property methods away to direct variable access during compilation, not so with interpreted JScript. A quick attempt at coding a simple Car object might produce code with property accessors:

function Car()
{
this.m_tireSize = 17;
this.m_maxSpeed = 250; // One can always dream!
this.GetTireSize = Car_get_tireSize;
this.SetTireSize = Car_put_tireSize;
}
function Car_get_tireSize()
{
return this.m_tireSize;
}
function Car_put_tireSize(value)
{
this.m_tireSize = value;
}
var ooCar = new Car();
var iTireSize = ooCar.GetTireSize();
ooCar.SetTireSize(iTireSize + 1); // An upgrade

The above is pretty good object oriented but terrible JScript. The extra indirection in accessing our local members is really hurting the performance of the application. Unless we need to do validation of the incoming values, the code should never add extra indirection and should be as precise as possible. Rewriting the above is an exercise in removing as much extra code as possible.

function Car()
{
this.m_tireSize = 17;
this.m_maxSpeed = 250; // One can always dream!
}
var perfCar = new Car();
var iTireSize = perfCar.m_tireSize;
perfCar.m_tireSize = iTireSize + 1; // An upgrade

We’ve removed two extra expando properties on the object, a couple of functions, some extra work while retrieving and setting our properties, and a few name binds. In short, try to stay away from any extra indirection.

For a more complete sample, we can also add prototypes. Note that prototypes will actually be slower, since the instance will be searched first, then the prototype, and so functional look-ups occur more slowly. This will make our naïve sample slower for sure. If you are creating thousands of instances of the class, the prototypes start to become more efficient. They start by reducing the size of each object since extra properties are not added per instance that point to the same global functions. Further, object instantiation can be much faster since the extra property assignments are not needed to set up all of the functions. For completeness, here is a full sample. As an extra challenge, try to find scenarios where the prototype car wins over the slow car.

<script>
// Slow Car definition
function SlowCar()
{
this.m_tireSize = 17;
this.m_maxSpeed = 250; // One can always dream!
this.GetTireSize = SlowCar_get_tireSize;
this.SetTireSize = SlowCar_put_tireSize;
}
function SlowCar_get_tireSize()
{
return this.m_tireSize;
}
function SlowCar_put_tireSize(value)
{
this.m_tireSize = value;
}
</script>

<script>
// Faster Car, no more property accessors
function FasterCar()
{
this.m_tireSize = 17;
this.m_maxSpeed = 250; // One can always dream!
}
</script>

<script>
// Prototype Car, use the language features!
function PrototypeCar()
{
this.m_tireSize = 17;
this.m_maxSpeed = 250; // One can always dream!
}

PrototypeCar.prototype.GetTireSize = function() { return this.m_tireSize; };
PrototypeCar.prototype.SetTireSize = function(value) { this.m_tireSize = value; };
</script>

<script>
function TestDrive()
{
var slowCar = new SlowCar(); // Safe and reliable, probably not fast
var fasterCar = new FasterCar(); // Lacks air-bags, probably faster
var protoCar = new PrototypeCar(); // Can technology win the day?

      var start = (new Date()).getTime();
for(var i = 0; i < 100000; i++) { slowCar.SetTireSize(slowCar.GetTireSize() + 1); }
var end = (new Date()).getTime();
output.innerHTML += "Slow Car " + (end - start) + "<br>";
     

      start = (new Date()).getTime();
for(var i = 0; i < 100000; i++) { fasterCar.m_tireSize += 1; }
end = (new Date()).getTime();
output.innerHTML += "Faster Car " + (end - start) + "<br>";

      start = (new Date()).getTime();
for(var i = 0; i < 100000; i++) { protoCar.SetTireSize(protoCar.GetTireSize() + 1); }
end = (new Date()).getTime();
output.innerHTML += "Prototype Car " + (end - start) + "<br>";
}
</script>

<button onclick="TestDrive();">Test Drive Cars!</button>
<div id="output"></div>

That’s all for Part 3.

Thanks,

Peter Gurevich
Program Manager

Justin Rogers
Software Development Engineer