IE + JavaScript Performance Recommendations - Part 1

IEBlog

Internet Explorer Team Blog

IE + JavaScript Performance Recommendations - Part 1

Hello again, this is Peter Gurevich, Performance PM (among other things) for IE7.  We have heard a lot of requests to improve our Jscript engine, especially now that AJAX sites are becoming more prevalent on the web.  I want you all to know that we have been listening and have recently made some great fixes to our engine to improve the garbage collection routine and to reduce unbounded memory growth.  You should see noticeable improvements on AJAX sites in the Release Candidate we shipped last week.  I want you also to know that performance of the object model and JavaScript engine will be an area that we focus on strongly in future releases.

While investigating the performance issues on script heavy sites we noticed several design patterns that resulted in less than optimal script performance.  Changing these design patterns on the site end often resulted in huge performance wins (4x to 10x increase) to the user, so I wanted to share these recommendations with everyone.

To that end, this blog will be the first in a 3 part series focusing on developing performance optimized scripts for web pages, covering the following:

  1. Symbolic Look-up recommendations
  2. JavaScript code inefficiencies
  3. Script and performance considerations specific to Internet Explorer

Please let me know if there are other useful performance topics you’d like to hear about.

Symbolic Look-up Recommendations

A primary source of JavaScript performance issues when running inside of IE come from constant symbolic look-up. Symbolic look-up occurs whenever the JScript engine tries to pair a name or identifier in the script with an actual object, method call, or property running in the context of the engine. Most of the time these objects are IE Document Object Model (DOM) objects and while there are general performance tips for working with any JScript host there are also specific IE considerations that can help when writing DHTML.

Evaluating Local Variables

Local variables need to be found based on a scope chain that resolves backwards from the most specific scope to the least specific. Sometimes these symbolic look-ups can pass through multiple levels of scope and eventually wind up in generic queries to the IE DOM that can be quite expensive. The worst case scenario is that your variable doesn’t yet exist and every scope in the chain is investigated, only to find that an expando variable needs to be created.

function WorkOnLocalVariable()
{
      local_variable = ObtainValueFromDOM();
      return (local_variable + 1);
}

Above is a sample of a poorly written function where we have a local_variable that we are attempting to define within the function scope, but without a preceding var declaration will actually be looked up in all scopes. If we don’t find the variable, a new global will be created, otherwise an existing global will be used. This new variable is now accessible to other methods as well and can sometimes cause odd behaviors in your code.

The recommendation here is to precede your variables with var if you are truly defining them in the current scope. This will prevent the look-up and your code will run much faster. You’ll also prevent aliasing against global named objects. A simple typo such as using the variable "status" without declaring it ("var status") will result in the use of the window.status property within the IE DOM, so be careful.

Cache Variables Whenever Possible

Every binding in JScript is late. This means each time you access a property, variable, or method a look-up is performed. Within the IE DOM, this could mean an extensive search of the element to find the same property over and over again, only to be returned to the JScript engine unchanged from the previous request. A simple example of overactive symbolic look-up on a DOM property follows.

function BuildUI()
{
      var baseElement = document.getElementById(‘target’);
      baseElement.innerHTML = ‘���; // Clear out the previous
      baseElement.innerHTML += BuildTitle();
      baseElement.innerHTML += BuildBody();
      baseElement.innerHTML += BuildFooter();
}

You have to imagine here that the functions are constructing HTML for inclusion into our base element. The above code results in many lookups of the property innerHTML and the construction of many temporary variables. For instance, the line where we clear the property does a look-up followed by a property set. This isn’t so bad. The next lines each do a property get for the initial text, followed by a string concatenation and then a property set. Both the property get and set involve name look-ups for the property. Ignore the string concatenation for now a faster version of this code would attempt to circumvent all of the extra name resolution.

function BuildUI()
{
      var elementText = BuildTitle() + BuildBody() + BuildFooter();
      document.getElementById(‘target’).innerHTML = elementText;
}

We now do a single property set of the target element and internally within the DOM the clearing operation is free (or as free as it can get for this example).

Another form of this same problem is in intermediate result caching. Often times code for web pages is written based on some top level base element where all interactions are going to start from. A perfect example is the document object. If I were going to write a simple calculator function based on values within the DOM it might be written like the following.

function CalculateSum()
{
      var lSide = document.body.all.lSide.value;
      var rSide = document.body.all.rSide.value;
      document.body.all.result.value = lSide + rSide;
}

In any compiled language the above will produce some heavily optimized code. Not so in JScript, since everything is interpreted and delay bound. Up front, JScript doesn’t know if the value property is going to manipulate the DOM and therefore change the values of the intermediate results. It can’t cache the intermediates all on its own. You’ll have to help it along. In the above case we can cache everything up to the all collection and improve our performance by eliminating multiple look-ups of three different variables.

function CalculateSum()
{
      var elemCollection = document.body.all; // Cache this
      var lSide = elemCollection.lSide.value;
      var rSide = elemCollection.rSide.value;
      elemCollection.result.value = lSide + rSide;
}

You can also cache functions. JScript and IE both handle this differently, so I’ll cover this in the following section.

Cache Function Pointers at all costs

Remember that everything is a look-up so calling the same function over and over again, also involves a look-up each time. Depending on if you are using a JScript function or an IE function pointer, these look-up operations will each entail a different amount of work. First, we’ll look at the simplest case of using a JScript function since they are quite a bit lighter than IE’s function pointers.

function IterateWorkOverCollection()
{
      var length = myCollection.getItemCount();
 
      for(var index = 0; index<length; index++)
      {
            Work(myCollection[index]);
      }
}

Normally you wouldn’t cast a second glance at that code, but if there are a significant number of elements you are operating on the constant look-ups performed to find the Work function can start to slow you down. Taking the Work function and assigning it to a local variable only takes a few extra milliseconds and can prevent the constant look-up.

function IterateWorkOverCollection()
{
      var funcWork = Work;
      var length = myCollection.getItemCount();
 
      for(var index = 0; index<length; index++)
      {
            funcWork(myCollection[index]);
      }
}

The speed savings in JScript for this type of operation are minimal, but within IE, when working with DOM functions you can get even more from this process. Internally, whenever you invoke a function off of an object, the script engine will do a name resolution, followed by an invoke of the target method. In addition, there is a local scope name lookup for the object itself. The same work loop using an IE element will definitely be more expensive and involve more look-ups.

function IterateWorkOverCollection()
{
      var parentElement = document.getElementById(‘target’);
      var length = myCollection.getItemCount();
 
      for(var index = 0; index<length; index++)
      {
            parentElement.appendChild(myCollection[iterate]);
      }
}

We still have the local variable look-up (which is faster than the function look-up from JScript), but then immediately following we have a function name look-up on the element (so we have two look-ups, ouch). Finally we get an invoke call as mentioned. We can speed this up by removing the function resolution entirely and creating a function pointer. A function pointer will encapsulate the name look-up as a DISPID and the object to call that function on and so the invoke process is more streamlined. The initial creation of the function pointer is slightly more expensive, but this creation is only incurred the first time you create a function pointer on a given element for a given method. The following code details the basics of IE function pointers, how they work, and how we can rewrite the work function to be slightly faster.

function GeneralFunctionPointerMagic()
{
      var myElement = document.getElementById(‘myElement’);
     
      // This creates our function pointer and involves a look-up + creation
      var funcAppendChild = myElement.appendChild;
 
      // Getting it a second time is faster, only does a look-up
      var funcAppendChild2 = myElement.appendChild;
 
      // Calling this is just like any other function pointer
      // Note this is a direct invoke with only a local variable look-up
      // to find funcAppendChild, with no IE DOM look-ups
      funcAppendChild(childElement);
}
 
function IterateWorkOverCollection()
{
      var funcAppendChild = document.getElementById(‘target’).appendChild;
      var length = myCollection.getItemCount();
 
      for(var index = 0; index<length; index++)
      {
            funcAppendChild(myCollection[index]);
      }
}

Caching function pointers isn’t a 100% guaranteed savings since we’ve shown there is overhead in the first creation and the look-ups involved. In cases where you only call the function a couple of times it probably doesn’t make sense to do the extra caching.

Avoid Using the ‘with’ Keyword

The ‘with’ keyword in JScript can be used to define a new scope local to the element you are working with. While this operation makes it easy to work on local properties it also modifies the scope chain making it more expensive to look up variables in other scopes. Further appending to the scope chain can take enough time that a simple usage of the ‘with’ statement to set only a small number of properties can be more expensive than writing more verbose code.

That’s all for Part 1.

Thanks,

Peter Gurevich
Program Manager

Justin Rogers
Software Development Engineer

  • Loading...