Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

316CHAPTER 8

Performance

Figure 8.9 The Drip tool allows detailed queries on the internal state of Internet Explorer’s DOM tree.

So far, we’ve looked at individual patterns and idioms for handling performance issues in small sections of code. When we write an Ajax application of even moderate size, the various patterns and idioms in each subsystem can interact with each other in surprising ways. The following section describes a case study that illustrates the importance of understanding how patterns combine with one another.

8.4.2A simple example

In our discussion thus far, we have covered the theory of memory management and described a few patterns that might help us when programmatically creating interface elements. In a real-world Ajax application, we will employ several patterns, which will interact with one another. Individual patterns have impacts on performance, but so do the interactions between patterns. It is here that having access to a common vocabulary to describe what your code is doing becomes very valuable. The best way to illustrate this principle is by example, so in this section we introduce a simple one and present the performance impact of varying the combination of patterns that it uses.

In the simple test program, we can repeatedly create and destroy small ClickBox widgets, so called because they are little boxes that the user can click on with

Designing for performance

317

 

 

the mouse. The widgets themselves have a limited behavior, described by the following code:

function ClickBox(container){

this.x=5+Math.floor(Math.random()*370);

this.y=5+Math.floor(Math.random()*370);

this.id="box"+container.boxes.length;

this.state=0;

this.render();

container.add(this);

}

ClickBox.prototype.render=function(){

this.body=null;

if (this.body==null){ this.body=document.createElement("div"); this.body.id=this.id;

}

this.body.className='box1';

this.body.style.left=this.x+"px";

this.body.style.top=this.y+"px";

this.body.onclick=function(){ var clickbox=this.backingObj; clickbox.incrementState();

}

}

ClickBox.prototype.incrementState=function(){ if (this.state==0){

this.body.className='box2'; }else if (this.state==1){

this.hide();

}

this.state++;

}

ClickBox.prototype.hide=function(){ var bod=this.body; bod.className='box3';

}

When first rendered, the ClickBoxes are red in appearance. Click on them once, and they turn blue. A second click removes them from view. This behavior is implemented by creating two-way references between the domain model object and the DOM element that represents it onscreen, as discussed earlier.

Programmatically, each ClickBox consists of a unique ID, a position, a record of its internal state (that is, how many clicks it has received), and a body. The body

318CHAPTER 8

Performance

is a DOM node of type DIV. The DOM node retains a reference to the backing object in a variable called backingObj.

A Container class is also defined that houses ClickBox objects and maintains an array of them, as well as a unique ID of its own:

function Container(id){ this.id=id;

this.body=document.getElementById(id); this.boxes=new Array();

}

Container.prototype.add=function(box){

this.boxes[this.boxes.length]=box;

this.body.appendChild(box.body);

}

Container.prototype.clear=function(){ for(var i=0;i<this.boxes.length;i++){

this.boxes[i].hide();

}

this.boxes=new Array(); report("clear"); newDOMs=0; reusedDOMs=0;

}

A screenshot of the application is shown in figure 8.10.

Figure 8.10 Our memory management demo application, after creation of the first 100 widgets. The user has just clicked one of the widgets with the mouse.

Designing for performance

319

 

 

The debug panel on the right reports on the internal state of the system after various user events, such as adding or removing widgets from the container.

The code has been written to allow us to swap in different patterns for creation and destruction of DOM elements and cyclic references while the application is running. The user may choose between these at runtime by checking and unchecking HTML form elements on the page. When the links that add or remove boxes from the container are activated, the combination of patterns that is used to implement the user interface will match the state of the checkboxes. Let’s examine each of these options and the corresponding code.

Reuse DOM Nodes checkbox

Checking this option will determine whether the ClickBox widget will try to find an existing DOM node when creating itself and create a new one only as a last resort. This allows the application to switch between the Create Always and Create If Not Exists patterns that we discussed in section 8.3.2. The modified rendering code follows:

ClickBox.prototype.render=function(){

this.body=null; if (reuseDOM){

this.body=document.getElementById(this.id);

}

if (this.body==null){ this.body=document.createElement("div"); this.body.id=this.id;

newDOMs++;

}else{

reusedDOMs++;

}

this.body.backingObj=this;

this.body.className='box1';

this.body.style.left=this.x+"px";

this.body.style.top=this.y+"px";

this.body.onclick=function(){ var clickbox=this.backingObj; clickbox.incrementState();

}

}

Unlink On Hide checkbox

When a ClickBox is removed from the container (either by a second click or by calling Container.clear()), this switch will determine whether it uses the Remove By Hiding or Remove By Detachment pattern (see section 8.3.2):

ClickBox.prototype.hide=function(){

var bod=this.body;

320CHAPTER 8

Performance

bod.className='box3'; if (unlinkOnHide){

bod.parentNode.removeChild(bod);

}

...

}

Break Cyclic References checkbox

When removing a ClickBox widget, this toggle determines whether the references between the DOM element and the backing object are reset to null or not, using the Break Cyclic References pattern in an attempt to appease the Internet Explorer garbage collector:

ClickBox.prototype.hide=function(){ var bod=this.body; bod.className='box3';

if (unlinkOnHide){ bod.parentNode.removeChild(bod);

}

if (breakCyclics){ bod.backingObj=null; this.body=null;

}

}

Form controls allow the user to add ClickBoxes to the container and to clear the container. The application may be driven manually, but for the purposes of gathering results here, we have also written a stress-testing function that simulates several manual actions. This function runs an automatic sequence of actions, in which the following sequence is repeated 240 times:

1Add 100 widgets to the container, using the populate() function.

2Add another 100 widgets.

3Clear the container.

The code for the stressTest function is provided here:

function stressTest(){

for (var i=0;i<240;i++){ populate (100); populate(100); container.clear();

}

alert("done");

}

Designing for performance

321

 

 

Note that the functionality being tested here relates to the addition and removal of nodes from the container element, not to the behavior of individual ClickBoxes when clicked.

This test is deliberately simple. We encourage you to develop similar stress tests for your own applications, if only to allow you to see whether memory usage goes up or down when changes are made. Designing the test script will be an art in itself, requiring an understanding of typical usage patterns and possibly of more than one type of usage pattern.

Running the stress test takes over a minute, during which time the browser doesn’t respond to user input. If the number of iterations is increased, the browser may crash. If too few iterations are employed, the change in memory footprint may not be noticeable. We found 240 iterations to be a suitable value for the machine on which we were testing; your mileage may vary considerably.

Recording the change in memory footprint was a relatively primitive business. We ran the tests on the Windows operating system, keeping the Task Manager open. We noted the memory consumption of iexplore.exe directly after loading the test page and then again after the alert box appeared, indicating that the test had completed. top or a similar tool could be used for testing on UNIX (see section 8.4.1). We closed down the browser completely after each run, to kill off any leaked memory, ensuring that each run started from the same baseline.

That’s the methodology, then. In the following section, we’ll see the results of performing these tests.

8.4.3Results: how to reduce memory footprint 150-fold

Running the stress test we just described under various combinations of patterns yielded radically different values of memory consumption, as reported by the Windows Task Manager. These are summarized in table 8.4.

Table 8.4 Benchmark results for ClickBox example code

ID

Reuse DOM Nodes

Unlink On Hide

Break Cyclic Refs

Final Memory Use (IE)

 

 

 

 

 

A

N

N

N

166MB

 

 

 

 

 

B

N

N

Y

84.5MB

 

 

 

 

 

C

N

Y

N

428MB

 

 

 

 

 

D

Y

N

N

14.9MB

 

 

 

 

 

E

Y

N

Y

14.6MB

 

 

 

 

 

continued on next page

322CHAPTER 8

Performance

Table 8.4 Benchmark results for ClickBox example code (continued)

ID

Reuse DOM Nodes

Unlink On Hide

Break Cyclic Refs

Final Memory Use (IE)

 

 

 

 

 

F

Y

Y

N

574MB

 

 

 

 

 

G

Y

Y

Y

14.2MB

 

 

 

 

 

The results in table 8.4 were recorded for the stress test on a fairly unremarkable workstation (2.8GHz processor, 1GB of RAM) for Internet Explorer v6 on Windows 2000 Workstation under various permutations of patterns. Initial memory use was approximately 11.5MB in all cases. All memory uses reported are the Mem Usage column of the Processes tab of the Task Manager application (see section 8.4.1).

Since we’re confronting real numbers for the first time, the first thing to note is that the application consumes quite a bit of memory. Ajax is often described as a thin client solution, but an Ajax app is capable of hogging a lot of memory if we make the right combination of coding mistakes!

The second important point about the results is that the choice of design patterns has a drastic effect on memory. Let’s look at the results in detail. Three of our combinations consume less than 15MB of RAM after rendering and unrendering all the ClickBox widgets. The remaining combinations climb upward through 80MB, 160MB, to a staggering 430MB and 580MB at the top end. Given that the browser was consuming 11.5MB of memory, the size of additional memory consumed has varied from 3.5MB to 570MB—that’s a difference of over 150 times, simply by modifying the combination of design patterns that we used. It’s remarkable that the browser continued to function at all with this amount of memory leaking from it.

No particular pattern can be identified as the culprit. The interaction between design patterns is quite complex. Comparing runs A, D, and F, for example, switching on the Reuse DOM pattern resulted in a huge decrease in memory usage (over 90 percent), but switching on Unlink On Hide at the same time generated a threefold increase! In this particular case, the reason is understand- able—because the DOM nodes have been unlinked, they can’t be found by a call to document.getElementById() in order to be reused. Similarly, switching on Unlink On Hide by itself increased memory usage against the base case (comparing runs C to A). Before we discount Unlink On Hide as a memory hog, look at runs E and G—in the right context, it does make a small positive difference.

Interestingly, there is no single clear winner, with three quite different combinations all resulting in only a small increase in memory. All three of these reuse