Export (0) Print
Expand All

More Performance Tips

As of December 2011, this topic has been archived. As a result, it is no longer actively maintained. For more information, see Archived Content. For information, recommendations, and guidance regarding the current version of Internet Explorer, see Internet Explorer Developer Center.

Michael Wallent
Microsoft Corporation

One of the most important parts of the development cycle here at Microsoft is the time we spend tuning the performance of our products. Performance tuning is something that developers see as a critical part of checking in a feature. The knowledge of how to tune a Win32® program has grown over the years, and has turned into a very large body of knowledge.

One of the problems that many DHTML and HTML developers run into is that there isn't a great body of knowledge on what makes a page fast, and what makes it slow. Sure, there are some easy things—such as don't use 2MB graphics on your pages. However, in some work we've done to make our DHTML pages speedier, we've found some very interesting tips and tricks that you can use to improve the performance of your own pages.

The example that I'll tune today is a table-building program. It builds a table with 1,000 rows, using the document.createElement() and element.insertBefore() methods. Each row contains a single cell. The content in each cell is "Text". How bad could this code be? How much tuning could there be in this small amount of code? Plenty.

I started off with a chunk of code that I thought would be fast. I tried not to do anything really stupid—such as not declare my variables, or use both Visual Basic® Scripting Edition (VBScript)and JScript® on the same page. Here's the starting point:

<HTML>
<BODY>
<SCRIPT>
  var tbl, tbody, tr, td, text, i, max;
  max = 1000;

  tbl = document.createElement("TABLE");
  tbl.border = "1";
  tbody = document.createElement("TBODY");
  tbl.insertBefore(tbody, null);
  document.body.insertBefore(tbl, null);
  for (i=0; i<max; i++) {
     tr = document.createElement("TR");
     td = document.createElement("TD");
     text = document.createTextNode("Text");
     td.insertBefore(text, null);
     tr.insertBefore(td, null);
     tbody.insertBefore(tr, null);
  }
</SCRIPT>
</BODY>
</HTML>

View the first sample.

I ran all the tests on an Intel Pentium II 233 with 64MB of RAM, running Windows NT® 4.0 and using Internet Explorer 5 (what else?). All the pages are loaded from a local drive. All timings are measured from the initial load of the page to when the page is "quiet" (all queued events have run, and the screen is repainted). This baseline page (I'll call it "Test 1") took 2328 milliseconds.

Some of the surprisingly time-expensive operations performed frequently in DHTML pages are references to global objects. The "document", the "body", the "window" -- references to all these objects are much more expensive than simply referring to a locally declared variable.

The first change I'll make to the page is to "cache" the reference to document.body in a variable called "theBody". There is only one other reference to this property, so this change should be pretty minor.

I added this line of code:

var theBody = document.body;

I then changed this line:

document.body.insertBefore(tbl, null);

to:

theBody.insertBefore(tbl, null);

View the second sample.

This change doesn't impact the overall time; it's only 3 m/s less to load -- well within the resolution of the test. However, if the code were referring to the document.body object in the primary loop, this change would have been significant.

The next change I made was to cache the reference to the document object itself. The document object is referred to 3002 times during the test. Here's the source for a version of the test that caches the document in a local variable, then refers to that local variable for all other references.

<HTML>
<BODY>
<SCRIPT>
  var tbl, tbody, tr, td, text, i, max;
  max = 1000;
  var theDoc = document;
  var theBody = theDoc.body;

  tbl = theDoc.createElement("TABLE");
  tbl.border = "1";
  tbody = theDoc.createElement("TBODY");
  tbl.insertBefore(tbody, null);
  theBody.insertBefore(tbl, null);
  for (i=0; i<max; i++) {
     tr = theDoc.createElement("TR");
     td = theDoc.createElement("TD");
     text = theDoc.createTextNode("Text");
     td.insertBefore(text, null);
     tr.insertBefore(td, null);
     tbody.insertBefore(tr, null);
  }
</SCRIPT>
</BODY>
</HTML>

View the third sample.

Running this version of the page only took 2100 m/s. This is a savings of almost 10 percent of the total time of the application, and is significant. Retrieving a document object takes about .4 m/s over the time it takes to fetch a local variable.

One document load speed optimization that we make quite often is to set the "defer" property on the <SCRIPT> tag. Setting this property is appropriate only when you don't have immediate script to run in a <SCRIPT> tag. (Immediate script is code that's not inside a function -- this code is evaluated as soon as the script block is loaded.) When the defer property is set, Internet Explorer doesn't have to wait to load and evaluate the script. This means that the page is loaded more quickly. Usually this means that immediate script should be relocated to a function, and that function should be set as the onload handler for the document or the body object. This would be very useful if you had only script that was dependent on a user action after the page loaded -- such as clicking a button, or moving the mouse over an area. However, when you have script that needs to run as, or just after, the page loads, then the gain usually isn't as great.

Here's the modified version that uses the defer property:

<HTML>
<BODY ONLOAD="init()">
<SCRIPT DEFER>
function init() {
  var tbl, tbody, tr, td, text, i, max;
  max = 1000;
  var theDoc = document;
  var theBody = theDoc.body;

  tbl = theDoc.createElement("TABLE");
  tbl.border = "1";
  tbody = theDoc.createElement("TBODY");
  tbl.insertBefore(tbody, null);
  theBody.insertBefore(tbl, null);
  for (i=0; i<max; i++) {
     tr = theDoc.createElement("TR");
     td = theDoc.createElement("TD");
     text = theDoc.createTextNode("Text");
     td.insertBefore(text, null);
     tr.insertBefore(td, null);
     tbody.insertBefore(tr, null);
  }
}
</SCRIPT>
</BODY>
</HTML>

View the fourth sample.

The time to load this page was 2043 m/s. This is a 12 percent improvement over the baseline, and a 2.5 percent improvement over the previous test.

The next enhancement was much more significant, but a little more involved. When you create elements and then insert them in the tree, it is more efficient to insert them into the primary document—not to insert them into a large subtree, and then insert the subtree into the primary document. For example, if I want to create a new table row with one cell, with some text in it, I could do the following:

  1. create <TR>
  2. create <TD>
  3. create TextNode
  4. insert TextNode into <TD>
  5. insert <TD> into <TR>
  6. insert <TR> into TBODY

But this method is slower than the following:

  1. create <TR>
  2. create <TD>
  3. create TextNode
  4. insert <TR> into TBODY
  5. insert <TD> into <TR>
  6. insert TextNode into <TD>

The previous tests all used the first method. Test 5 uses the second method from above. Here's the actual code:

<HTML>
<BODY ONLOAD="init()">
<SCRIPT DEFER>
function init() {
  var tbl, tbody, tr, td, text, i, max;
  max = 1000;
  var theDoc = document;
  var theBody = theDoc.body;

  tbl = theDoc.createElement("TABLE");
  tbl.border = "1";
  tbody = theDoc.createElement("TBODY");
  tbl.insertBefore(tbody, null);
  theBody.insertBefore(tbl, null);
  for (i=0; i<max; i++) {
     tr = theDoc.createElement("TR");
     td = theDoc.createElement("TD");
     text = theDoc.createTextNode("Text");
     tbody.insertBefore(tr, null);
     tr.insertBefore(td, null);
     td.insertBefore(text, null);
  }
}
</SCRIPT>
</BODY>
</HTML>

View the fifth sample.

Test 5 took only 1649 m/s. This is almost 25 percent faster than the previous test, and brings the performance of the total test to be almost 30 percent faster than the baseline.

The next change was to modify the table to use fixed-table layout. The width of columns in a table with fixed-table layout are set either by the <COL> tags in the table, or, if there are no <COL> tags, the space for each cell is evenly divided for the row. The fixed-table layout style improves the performance of tables, because the size of the content of each cell doesn't have to get calculated. This is usually a boon to performance, especially on large tables with many columns.

Adding this was as easy as adding a cascading style sheet (CSS) style:

tbl.style.tableLayout = "fixed";

View the sixth sample.

However, because there was only one cell in the table, this improved the performance of the page by only 1.6 percent. The increase would have been larger if there were more cells in the table.

The final two tests involved changing the way the text was inserted into the table cell. In all the other tests, a TextNode was created, and then inserted into the TD. In Test 7, instead of inserting a text node, the text is specified with innerText. Here's the code change:

td.innerText = "Text";

View the seventh sample.

Surprisingly, this made a large difference—a 9 percent improvement over the last test, which brought the total performance improvement to 36 percent. The time went from 2323 m/s in the baseline to 1473 m/s in the final test.

By now, almost everyone knows that using element.innerHTML is slow, slow, slow. To see just how slow it was, I made a final test page that used innerHTML instead of innerText to insert "Text" into the cell. This literally crushed performance. Total time went to 3375 m/s, more than 80 percent worse than the previous test. This one change brought the total baseline comparison to -45 percent. Clearly, innerHTML is pretty expensive.

You can view all the results, showing the trials and the averages:

An interesting side note: I created that page with Excel 2000, just as you would create a normal spreadsheet. After I was done, I used the Save As Web Page feature to convert it to HTML—creating the cool page you see on the link above. It turns out that I hadn't added the Difference from Previous line, so I went back and reopened the HTML page in Excel 2000, expecting some round-tripping problem. But no! It was perfect, 100 percent fidelity. You can open up that page in your own copy of Excel 2000 to see for yourself. I liked it, and you don't need to load a special viewer to see the page.

Tuning HTML pages is just like tuning Win32 applications; you have to know what's slow, and what's fast. Hopefully these tips will help you speed up your own pages.

 

DHTML Dude

Michael Wallent is Microsoft's group program manager for Internet Explorer.


  
Show:
© 2014 Microsoft