전송측 parameter를 escape(encodeURIComponent(파라미터정보)) 감싸고
수신측 decodeURIComponent 를 사용해서 한글 깨짐현상 방지 가능

WRITTEN BY
hopangbear

트랙백  0 , 댓글  0개가 달렸습니다.
secret

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


WRITTEN BY
hopangbear

트랙백  0 , 댓글  0개가 달렸습니다.
secret

IE+JavaScript Performance Recommendations Part 2: JavaScript Code Inefficiencies

Hello again, this is Peter Gurevich, Performance PM for IE. We have gotten a lot of good feedback from our first post on IE + JavaScript Performance Recommendations so I am eager to hear what you think of our second installment. This is the first of two posts concerning inefficiencies in Javascript code itself.

JScript Code Inefficiencies – Chapter One

There are plenty of ways to use JScript (and other Javascript engines) inefficiently since the interpreter performs almost zero optimizations when interpreting code. Most of the recommendations in the previous post are where a rather large inefficiency shows itself in many different forms. Here we’ll be concentrating on specific inefficiencies related to string manipulation and dynamic code evaluation.

Optimize String Manipulations by Avoiding Intermediate Results

Most JScript code used for manipulating the IE DOM requires some sort of string manipulation. While most of the performance that is lost in string manipulation is due to name resolution and binding of the properties on objects you are working with, much of it can also be explained away by making the wrong assumptions about how strings are handled.

The first piece of erroneous code comes in using the standard string concatenation operator + when working with strings. Strings are stored in Jscript on the heap and a pointer to them is located within the garbage collector. Each intermediate string manipulation results in another temporary allocation of memory. Most languages have some construct for intermediate string results, such as a StringBuilder in .NET or a buffered string class in C++. For the JScript language buffered strings are created using the Array.join function. The following sample creates a larger string by concatenating together many smaller strings.

var smallerStrings = new Array();
// Fill the array with smaller strings
var myRatherLargeString = "";
var length = smallerStrings.length;
for(var index = 0; index<length; index++)
{
    myRatherLargeString += smallerStrings[index];
}

Performance articles often note this type of construct is slow. Looks can be deceiving here since the extra overhead of obtaining the smaller, intermediate strings is making this slow. The above code sample would work much better when rewritten using Array.Join.

var myRatherLargeString = smallerStrings.join(‘’);

Not only is the code simpler, but it is also much faster for this case. When the number of values being concatenated is small enough the concatenation operator still wins. The cross-over point is not entirely deterministic, but exists somewhere in the neighborhood of 7-13 values. For larger strings the count may be much smaller. You’ll have to do performance testing when you choose this approach and make sure it fits your algorithm. The following code sample is optimized to point out the cut-off point.

<script>
var markupBuilder = new Array();
markupBuilder.push("<div id=\"");
markupBuilder.push("sampleID");
markupBuilder.push("\">");
markupBuilder.push("sampleText");
markupBuilder.push("</div>");
var markupBuilder2 = new Array();
markupBuilder2.push("<div id=\"");      // 0
markupBuilder2.push("sampleID");        // 1
markupBuilder2.push("\">");             // 2
markupBuilder2.push("sampleText");      // 3
markupBuilder2.push("</div>");          // 4
markupBuilder2.push("<div id=\"");      // 5
markupBuilder2.push("sampleID");        // 6
markupBuilder2.push("\">");             // 7
markupBuilder2.push("sampleText");      // 8
markupBuilder2.push("</div>");          // 9
markupBuilder2.push("<div id=\"");      // 10
markupBuilder2.push("sampleID");        // 11
markupBuilder2.push("\">");             // 12
markupBuilder2.push("sampleText");      // 13
markupBuilder2.push("</div>");          // 14
markupBuilder2.push("<div id=\"");      // 15
markupBuilder2.push("sampleID");        // 16
markupBuilder2.push("\">");             // 17
markupBuilder2.push("sampleText");      // 18
markupBuilder2.push("</div>");          // 19
function buildDivJoin(id, innerText, short)
{
    if ( short )
   {
        markupBuilder[1] = id;
        markupBuilder[3] = innerText;
        return markupBuilder.join("");
   }
     else
   {
        markupBuilder2[1] = id;
        markupBuilder2[3] = innerText;
        markupBuilder2[6] = id;
        markupBuilder2[8] = innerText;
        markupBuilder2[11] = id;
        markupBuilder2[13] = innerText;
        markupBuilder2[16] = innerText;
        markupBuilder2[18] = innerText;
        return markupBuilder2.join("");
   }
}
function buildDivConcat(id, innerText, short)
{
    if ( short )
   {
        return "<div id=\"" + id + "\">" + innerText + "</div>";
    }
    else
    {
        return "<div id=\"" + id + "\">" + innerText + "</div>" +
            "<div id=\"" + id + "\">" + innerText + "</div>" +
            "<div id=\"" + id + "\">" + innerText + "</div>" +
            "<div id=\"" + id + "\">" + innerText + "</div>";
    }
}
function perfTestDivs()
{
    var start = (new Date()).getTime();
    for(var i = 0; i < 25000; i++) { buildDivJoin("foo", "innerText", true); }
    var end = (new Date()).getTime();
    output.innerHTML += "Join short " + (end - start) + "<br>";
    start = (new Date()).getTime();
    for(var i = 0; i < 25000; i++) { buildDivConcat("foo", "innerText", true); }
    end = (new Date()).getTime();
    output.innerHTML += "Concat short " + (end - start) + "<br>";
    var start = (new Date()).getTime();
    for(var i = 0; i < 10000; i++) { buildDivJoin("foo", "innerText", false); }
    var end = (new Date()).getTime();
    output.innerHTML += "Join long " + (end - start) + "<br>";
    start = (new Date()).getTime();
    for(var i = 0; i < 10000; i++) { buildDivConcat("foo", "innerText", false); }
    end = (new Date()).getTime();
    output.innerHTML += "Concat long " + (end - start) + "<br>";
}
function perfTest()
{
    output.innerHTML = "";
    perfTestDivs();
}
</script>
<body>
<button onclick="perfTest()">Performance Test</button>
<div id="output"></div>
</body>

Even with the concatenation operator winning every now and then it isn’t always best to use it when you are unsure about the destination of your operation. As noted in the symbolic look-up section in the previous post , it can be very expensive to work on a DOM property  such as innerHTML and concatenate text a little bit at a time. Even worse, property operations are not guaranteed to round trip so operations performed bit by bit against the DOM may not be the same as performing them all at once.

<head>
<script>
function InjectMangle()
{
      var strContent = "<object>Fallback Text</object>";
      var oDiv = document.getElementById("injectSpot");
      oDiv.innerHTML = strContent;
      setTimeout("CheckInject()", 1000);
}
function CheckInject()
{
      var oDiv = document.getElementById("injectSpot");
      alert(oDiv.innerHTML);
}
</script>
</head>
<body>
<button onclick="InjectMangle();">Perform Injection Check</button>
<div id="injectSpot"></div>
</body>

The general wisdom here is to first create your final strings in local variables if possible and then copy them into the properties on your objects. Especially if the properties you are working with may change their values if you do your work in parts. Concatenation of large strings is done much faster if you use Array.join and you’ll also save a lot of memory that would have been used for storing intermediate string results. Finally, never use the concatenation operator if you are working with properties on objects within IE or any other ActiveX control. If at all possible it is best to avoid the extra name look-ups and potentially expensive property accesses.

Running Code Using the ‘eval’ Statement is Expensive

The title explains it all. The eval statement in JScript is both expensive in terms of performance and prone to error if the site is generating dynamically the script to be run. If you can remove it from your application you should do so. You’ll need to replace it with functional equivalents where the dynamically evaluated code can be simulated by changing the input parameters. This general wisdom not only leads to faster code in most cases but can also make your program more reliable and easy to understand. If arbitrary code is executing on the client machine it becomes hard to determine how it failed.

For those interested in the “why”, execution of an arbitrary string of script involves the construction of a new script runtime, copying the context of the currently executing script, manipulation of the runtime stack, and a bit of other work. This isn’t much different from running other code, but isn’t nearly as fast as writing the inline branching code to handle all of the possibilities of the dynamically generated code. There are obviously good uses for the eval statement, just examine your project thoroughly before taking a dependency on it.

Requirements of Eval for JSON Expressions

Now that we’ve warned against eval let’s talk about when it can be useful! JSON is becoming more and more popular as a data protocol written on top of the JScript language. Along with any protocol it has concerns for security and tampering and therein lies a problem. Security means validating large chunks of data for potentially malicious code and of course all of this takes time. The purpose behind JSON was to quickly and easily transfer data into in memory usable structures, something faster than parsing, and in some cases it can actually be slower so you have to be careful. Those cases where it tends to be slower are when your data strings begin to get extremely large. The evaluation time of the security regular expression and creating a temporary copy of the script to be evaluated (both properties of the current standard JSON parser) start to add up. When your JSON requests start generating 200K character strings you might want to think about your process.

What can you do here? Well, if you use JSON follow the standards and don’t try to change the published protocols since that could negatively affect the performance in one or more browsers that you don’t test against. Make sure you put constraints on your data sizes so that the JSON protocol evaluation doesn’t take too long and hang the browser. Remember, while those structures are being built, nothing else can be done.

Switch Blocks are Linear Evaluation Tables

The switch statement doesn’t know that the values are sorted in some way that lends itself to effectively a nested if structure – most languages will heavily optimize this, as Justin talked about on his blog a couple of years ago, but JavaScript doesn’t. If you have a large switch it is best to break this down into a series of nested if statement comparisons, an array where you can perform a binary search, or my personal favorite a sparse array (hash-table). The following code sample translates id values to their string identifiers. This is a common feature to reduce the amount of data transferred between the client and the server.

<script>
function mapIdToString(id)
{
    switch(id)
    {
        case 0:
            return "User Record";
        case 1:
            return "Management Record";
        case 2:
            return "Product Record";
        case 3:
            return "Sale Record";
        default:
            return "Product Identifier " + id;
    }
}
</script>

As you can see we have a couple of common cases and a default clause that maps anything left over. We might be calculating a few thousand of these over and over again and running through the same switch. As we add more known values the number of comparisons increases linearly. At some point the switch becomes increasingly slower than writing the expanded set of if statements with range checks (a static binary search of the data). In fact, a simple optimization depending on the data, might just be to add a check for any value that would end up in the default clause and prevent doing all of the extra known value checks just to create a new product identifier.

For larger data sets you can use an array to map id’s to names based on indexing into the array. If the id is a sparse value (not incrementing) then you might need to sort the array and perform a binary search for the id of interest. This requires maintaining some structure that can hold both the id and the string value and is outside of the scope of the article, implementing the sort algorithm for this structure, and creating a binary search routine in JScript. While we could add that type of information here the next option is much easier to implement and provides nearly all of the same benefits.

JScript maintains an associative array (also known as a hash-table) that can be used for any mapping operations. A simple HTML tag parser might take advantage of this by inserting functions for handling a particular tag type into a hash-table indexed by tag name. This would be significantly more robust and surprisingly faster than the equivalent switch statement. As a technical note on the script itself, we’ve placed a try…catch block to catch malformed input. Where this block should reside and what happens on error is up to the algorithm. The normal switch statement is already robust to malformed input.

<script>
function Parser_Div(tokenizer, perf)
{
      if ( !perf )
            output.innerHTML += "Parsing a DIV<br>";
}
function Parser_Span(tokenizer, perf)
{
      if ( !perf )
            output.innerHTML += "Parsing a SPAN<br>";
}
function Parser_Para(tokenizer, perf)
{
      if ( !perf )
            output.innerHTML += "Parsing a P<br>";
}
var o = new Array();
o["DIV"] = Parser_Div;
o["SPAN"] = Parser_Span;
o["P"] = Parser_Para;
function Parser(perf)
{
      if ( !perf )
            output.innerHTML = ""; // Clear our output
      var tokenizer = source.innerText.split(' ');
      for(var i = 0; i < tokenizer.length; i++)
      {
            o[tokenizer[i]](tokenizer, perf);
      }
}
function SwitchParser(perf)
{
      var tokenizer = source.innerText.split(' ');
      for(var i = 0; i < tokenizer.length; i++)
      {
            switch(tokenizer[i])
            {
                  case "DIV": Parser_Div(tokenizer, perf);
                  case "SPAN": Parser_Span(tokenizer, perf);
                  case "P": Parser_Para(tokenizer, perf);
            }
      }
}
function Perf()
{
      output.innerHTML = "";
      var start = (new Date()).getTime();
      for(var i = 0; i < 5000; i++) { Parser(true); }
      var end = (new Date()).getTime();
      output.innerHTML += "Hash Parser " + (end - start) + "<br>";
      start = (new Date()).getTime();
      for(var i = 0; i < 5000; i++) { SwitchParser(true); }
      end = (new Date()).getTime();
      output.innerHTML += "Switch Parser " + (end - start) + "<br>";
      start = (new Date()).getTime();
      for(var i = 0; i < 5000; i++) { Parser(true); }
      end = (new Date()).getTime();
      output.innerHTML += "Hash Parser " + (end - start) + "<br>";
      start = (new Date()).getTime();       for(var i = 0; i < 5000; i++) { SwitchParser(true); }
      end = (new Date()).getTime();
      output.innerHTML += "Switch Parser " + (end - start) + "<br>";
}
</script>
<button onclick="Parser(false);">Parse!</button>
<button onclick="Perf();">Perf!</button>
<div id="source">DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P DIV SPAN P</div>
<div id="output"></div>

For such a small number the switch is likely faster, as we start growing the number of tags handled the hash-table will eventually win. Depending on the format of your data make use of the appropriate constructs and avoid the switch when a large number of comparisons are going to be needed.

That’s all for Part 2.

Thanks,

Peter Gurevich
Program Manager

Justin Rogers
Software Development Engineer


WRITTEN BY
hopangbear

트랙백  0 , 댓글  0개가 달렸습니다.
secret

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

Published Monday, August 28, 2006 2:59 PM by ieblog

WRITTEN BY
hopangbear

트랙백  0 , 댓글  0개가 달렸습니다.
secret