IE + JavaScript Performance Recommendations - Part 1

IEBlog The Windows Internet Explorer Weblog

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

  • You CalculateSum() example is hilarious, not only because of the use of document.all that has been deprecated since IE5 and should have been removed completely from IE7 because this backwards-compatibilty still causes global namespace pollution and many quirks, but mainly because a value-attribute from the DOM is always a string so you are not actually adding up 2 values, you are concatenating them.

    For the rest a lot of this seems like premature optimization to me. Maybe you should mention something about the ultra-slow stringhandling of Microsoft's JScript and the way to overcome that by using arrays and join('') (yes, it's ugly).
    When will you fix the simple things like substr(-2)?
  • I find Tino's comments hilarious, because he totally missed the point of the article.
  • For those asking about IE7 performance improvements there is the first of a set of blog posts covering...
  • would be nice to see support for Javascript 1.7
  • Tino's comments aren't bad at all. My example was slightly contrived and does require a specialized control in order to work properly. If you wanted it to work with the default value property on a standard DOM element you'd need to do some conversion. The focus of the article was specifically on the things you could do to improve performance, not really ensure that each code sample stood on it's own right. In fact, we've found most people already have plenty of their own samples that they can apply these changes to for immediate performance wins.

    Since I'm on the IE team, I can't really comment on "features" of the JScript language directly. In fact, most of the goodness above is about how JScript works, but is also very much applicable to how the IE DOM is constructed and how we interact with the JScript engine. I'd like to keep any postings we do on the topic of improving the link between the two components and not on the internal specifics of components I don't work on. If you have problems with IE on the other hand, we may be able to help out. So feel free to ask any JScript+IE integration questions or maybe just about IE performance issues you've had.
  • The problem with these examples is that they lead to unnecessary code obfuscation. As Tino noted, stay away from premature optimalization.

    If I remember correctly one thing that slows JScript down is it's garbage collection: if you have a lot of objects it'll check them all, everytime, even if they stay in existance for a long time. It would be more efficient to decrease the checking of long-existing objects.

    I'm also interested in how you are solving the memory leaks. Do you know about http://novemberborn.net/javascript/edgvl ?
  • yes, yes, yes! more plz! ;)
  • Script optimization is always a good thing (though Tino's right, use of document.all should be avoided if possible).

    I noticed that there isn't any word about the W3C Event Model, but I assume that's because in order to implement it, IE's entire event model would have to be rewritten (seeing as IE starts on the target element and bubbles the event upwards by default).
  • Justin -

    Please pass along my thanks and 'excellent work' on the new version of JScript. We are doing heavy, heavy AJAX and have noticed significant speedups (not quite as fast as Mozilla on 'large working sets' but much, much faster than JScript 5.6).

    Speaking of which, the new JScript 5.7 .dll seems to work fine when installed in a system using IE6 (and the performance improvements remain). Any chance we'll see this new .dll as part of a 'security patch update' or whatever to IE6?

    Thanks again!! I owe you guys some major beers :-)

    Cheers,

    - Bill
  • Ooops - forgot to thank Peter for an excellent article. Looking forward to the next one :-)!

    Cheers,

    - Bill
  • Installed the new IE7 RC1, after the installation any flash pages have stopped working, even after several installs of flash player the problem persists.
  • Good article, thanks!
  • Try the following:

    var f = document.body.insertBefore;

    alert(typeof f); // throws exception, should return "function"

    alert(f instanceof Function); // throws exception, should return true

    f.apply(document.body, [newChild, oldChild]); // throws as well

    Yawn... This has been broken since IE4. No wonder IE is the Netscape of today.
Page 1 of 9 (134 items) 12345»