Chapter 17

Bandwidth and Performance Optimizations

WHAT YOU WILL LEARN IN THIS CHAPTER:

  • Shrinking the size of the assets of your app
  • Optimizing performance of your JavaScript
  • Writing efficient code

In a decade where broadband is now the norm, many web developers have allowed their sites and applications to be composed of ill-formed HTML, massive JavaScript libraries, and multiple CSS style sheets.

However, when you are developing applications for iPhone, iPad, and iPod touch, you need to refocus your programming and development efforts toward optimization and efficiency. What makes developing IOS apps different from normal web apps is that the developer can no longer rely on the fact that the user is accessing the application from a broadband connection. iPhone and iPad users may be coming to your application using Wi-Fi or a slower 3G connection.

Therefore, as you develop your applications, you should formulate an optimization strategy that makes the most sense for your context. You need to think about both bandwidth and code performance optimizations.

OPTIMIZATION STRATEGIES

If you spend much time at all researching optimization strategy and techniques, you quickly discover that there are two main schools of thought. I refer to the first camp as hyper-optimizers. Hyper-optimizers do almost anything to save a byte or an unneeded call to the web server. They are far more concerned with saving milliseconds than they are about the readability of the code that they are optimizing. The second camp, perhaps best described as relaxed optimizers, are interested in optimizing their applications. But they are not interested in sacrificing code readability and manageability in an effort to save a nanosecond here or there.

Decide which camp you fall into. Be careful that don’t go through complex optimization hoops unless you are able to prove that your steps are going to make a substantive difference in the usability of your application. Many optimization techniques that people advocate offer nominal performance boosts, but end up making your code much harder to work with and maintain.

BEST PRACTICES TO MINIMIZE BANDWIDTH

Arguably the greatest bottleneck of any IOS web app is the time it takes to transport data from the web server to Safari, especially if your application is running over 3G. You can certainly opt for local offline caching. However, you should also consider the following techniques as you assemble your web application.

General

Consider the following general tips as you develop web apps for IOS as well as mobile-friendly web sites.

  • Separate your page content into separate .css, .js, and .html files so that Safari on IOS can cache each file.
  • Studies show that loading additional files takes 60% to 90% of the total load time of a web page. Therefore, be sure to minimize the total number of external style sheets, JavaScript library files, and images you include with your page. Because browsers typically make two to four requests at a given time, every additional file that a browser has to wait on for the request to complete creates latency.
  • Reduce white space (tabs and spaces) wherever possible. Although this might seem like a nominal issue, the amount of excess white space can add up, particularly on a larger-scale web application with dozens of files.
  • Remove useless tags and unused styles and JavaScript functions in your HTML, CSS style sheets, and JavaScript library files.
  • Remove unnecessary comments. However, keep in mind the following caveat: Removing comments can reduce file size, but it can make it harder to manage your code in the future.
  • Use shorter filenames. For example, it is much more efficient to reference tb2.png than TopBannerAlternate2_980.png.
  • Write well-formed and XML-complaint XHTML code. Although this is not a bandwidth issue, well-formed XHTML requires fewer passes and parsing by Safari before it renders the page. As a result, you can improve the time from initial request to final display through this coding practice.
  • Consider using gzip compression when you serve your application. (See the following section for more on compression options.)
  • Consider using a JavaScript compressor on your JavaScript libraries. You could then work with a normal, unoptimized JavaScript library for development (mylibrary.js) and then output a compressed version for runtime purposes (mylibrary-c.js). (See the “Compressing Your Application” section for more on compression options.)

Images

Images can often be the single greatest bottleneck for performance in many web apps. Pay attention to the following tips to optimize how you use images in your app.

  • Large image sizes are a traditional bottleneck that you should target for your applications. Be meticulous in optimizing the file size of your images. Shaving off 5KB or so from several images in your application can make a notable performance increase.
  • Make sure your images are sized appropriately for display on the iPhone, iPad, and iPod touch viewports. Never, ever, rely on browser scaling. Instead, match image size to image presentation.
  • Image data is more expensive than text data. Therefore, consider using canvas drawing in certain cases.
  • Instead of using image borders, consider using CSS borders instead, particularly with the enhanced -webkit-border-radius property.
  • Instead of using one large background image, consider using a small image and tiling it.
  • Use CSS Sprites to combine multiple image files into a larger one. The overall latency impact is reduced because fewer requests are made to the server.

CSS and JavaScript

The manner in which you structure your CSS style sheets and JavaScript code can impact performance. Consider these tips to optimize an often-overlooked area of your code.

  • Combine rules to create more efficient style declarations. For example, the second declaration is much more space efficient than the first one:
    // Less efficient
    div #content {
                    font-family: Helvetica, Arial, sans-serif;
                    font-size: 12px; /* Randy: do we want this as px or pt? */
                    line-height: 1.2em; /* Let's try this for now.*/
                    font-weight: bold;
    }
    // More efficient
    div #content {font: bold 12px/1.2em Helvetica, Arial, sans-serif};
  • Consider using shorter CSS style names and JavaScript variable and function names. After all, the longer your identifiers are, the more space your files take. At the same time, do not make your identifiers so short that they become hard to work with. For example, consider the trade-offs with the following three declarations:
    /* Inefficient */
    #homepage-blog-subtitle-alternate-version{letter-spacing:.1em;}
    /* Efficient, but cryptic */
    #hbsa{letter-spacing:.1em;}
    /* Happy medium */
    #blog-subtitle-alt{letter-spacing:.1em;}

As you work through these various strategies and test results, a good way to check the total page size is to save the page as a web archive in a desktop version of Safari. The file size of the archive file indicates the HTML page size with all the external resources (images, style sheets, and script libraries) associated with it.

COMPRESSING YOUR APPLICATION

Normally, an IOS web application is launched when a user types the URL in his Safari browser (or clicks its shortcut icon on the home screen). The web server responds to the HTTP request and serves the HTML file and each of the many supporting files that are used in the display and execution of the web app. Although you might have image files optimized as much as possible to minimize bandwidth, each uncompressed HTML file, CSS style sheet, and JavaScript library file requested always takes up much more space than if it were compressed. Therefore, several options are available to compress files or JavaScript code on the fly on the server.

Gzip File Compression

Safari on IOS provides support for gzip compression, a compression option offered by many web servers. Using gzip compression, you can reduce the size of HTML, CSS, and JavaScript files and reduce the total download size by up to four to five times. However, because Safari must uncompress the resources when it receives them, be sure to test to ensure that this overhead does not eliminate the benefits gained.

For example, if you want to turn on gzip compression in PHP, you could use the following code:

<?php
ob_start("ob_gzhandler");
?>
<html>
<body>
<p>This page has been compressed.</p>
</body>
</html>

JavaScript Code Compression

In addition to reducing the total file size of your website, another technique is to focus on JavaScript code. These compression strategies go far beyond the manual coding techniques described in this chapter and seek to compress and minify — remove all unnecessary characters — your JavaScript code. In fact, using these automated solutions, you can potentially reduce the size of your scripts by 30% to 40%.

There are a variety of open-source solutions for compressing JavaScript code, and they tend to take one of two approaches. The safe optimizers remove whitespace and comments from code but do not seek to actually change naming inside of your source code. The aggressive optimizers go a step further and seek to crunch variable and function names. Although the aggressive optimizers achieve greater compression ratios, they are not as safe to use in certain situations. For example, if you have eval() or with in your code (even though they’re not recommended), these routines will be broken during the compression process. What’s more, some of the optimizers, such as Packer, use an eval-based approach to compress and uncompress. However, there is a performance hit in the uncompression process, and in certain conditions your script could be slowed down.

Here are some of the options available (ranked in order of conservatism employed in their algorithms):

  • JSMin (JavaScript Minifier — www.crockford.com/javascript/jsmin.html) is perhaps the best-known JavaScript optimizer. It is the most conservative of the optimizers, focusing on simply removing whitespace and comments from JavaScript code.
  • YUI Compressor (www.julienlecomte.net/blog/2007/08/13/introducing-the-yui-compressor) is a recently introduced optimizer that claims to offer a happy medium between the conservative JSMin and the more aggressive ShrinkSafe and Packer.
  • Dojo ShrinkSafe (www.dojotoolkit.org/docs/shrinksafe) optimizes and crunches local variable names to achieve greater compression ratios.
  • Dean Edwards’s Packer (dean.edwards.name/packer) is an aggressive optimizer that achieves high compression ratios.

Deciding which of these options to use depends on your specific needs and the nature of your source code. I recommend starting on the safe side and moving up as needed.

If you decide to use one of these optimizers, make sure you use semicolons to end your lines in your source code. Besides being good programming practice, most optimizers need them to accurately remove excess whitespace.

Although Packer requires semicolons, Dojo ShrinkSafe does not require them and actually inserts missing semicolons for you. So you can preprocess a JavaScript file through ShrinkSafe before using it in a compressor that requires semicolons such as Packer.

To demonstrate the compression ratios that you can achieve, I ran a sample JavaScript library file through several of these optimizing tools. Table 17-1 includes the results.

TABLE 17-1: Benchmark of JavaScript Library Compression

COMPRESSOR JAVASCRIPT COMPRESSION (BYTES) WITH GZIP COMPRESSION (BYTES)
No compression 100% (11284) 26% (2879)
JSMin 65% (7326) 21% (2403)
Dojo ShrinkSafe 58% (6594) 21% (2349)
YUI Compressor 64% (7211) 21% (2377)
YUI Compressor (with Munged) 46% (5199) 18% (2012)
YUI Compressor (with Preserve All Semicolons) 64% (7277) 21% (2389)
YUI Compressor (with Munged and Preserve All Semicolons) 47% (5265) 18% (2020)

One final option worth considering is a PHP-based open-source project called Minify. Minify combines, minifies, and caches JavaScript and CSS files to decrease the number of page requests that a page has to make. To do so, it combines multiple style sheets and script libraries into a single download (code.google.com/p/minify).

JAVASCRIPT PERFORMANCE OPTIMIZATIONS

The performance of JavaScript on IOS is slower than on the Safari desktop counterparts, although recent improvements in the IOS shrink this gap considerably. For example, consider the following simple DOM-access performance test:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Performance Test</title>
</head>
<body>
<form id="form1">
<input id="i1" value="zero" type="text">
</form>
<div id="output"></div>
</body>
<script type="application/x-javascript">
var i = 0;
var start1 = new Date().getTime();
divs = document.getElementsByTagName('div'),
for(i = 0; i < 80000; i++)
{
        var d = divs[0];
}
var start2 = new Date().getTime();
var delta1 = start2 - start1;
document.getElementById("output").innerHTML = "Time: " + delta1;
</script>
</html>

Safari for Mac OS X executes this script in 529 milliseconds, while Safari for IOS takes 13,922 milliseconds. That’s more than 26 times longer! Therefore, in addition to the optimizations you can make in shrinking the overall file size of your application, you should also give priority to making performance gains in execution based on your coding techniques. The following sections cover several best practices you should consider.

Smart DOM Access

When working with client-side JavaScript, accessing the DOM can be at the heart of almost anything you do. However, as essential as these DOM calls may be, it is important to remember that DOM access is expensive from a performance standpoint and so should be done with forethought.

Cache DOM References

Cache references that you make to avoid multiple lookups on the same object or property. For example, compare the following inefficient and efficient routines:

// Ineffecient
var str = document.createTextNode('Farther up, further in'),
document.getElementById('para1').appendChild(str);
document.getElementById('para1').className="special";
// More efficient
var str = document.createTextNode('Farther up, further in'),
var p = document.getElementById("para1");
p.appendChild(str);
p.className="special";

What’s more, if you make a sizeable number of references to a document or another common DOM object, cache them, too. For example, compare the following:

// Less efficient
var l1=document.createTextNode("Line 1");
var l2=document.createTextNode("Line 2");
// More efficient
var d=document;
var l1=d.createTextNode("Line 1");
var l2=d.createTextNode("Line 2");

If you reference document a handful of times then it is probably not practical to go through this trouble. However, if you find yourself writing document a thousand times in your code, the efficiency gains make this practice worth considering.

Offline DOM Manipulation

When you are writing to the DOM, assemble your subtree of nodes outside of the actual DOM and then insert the subtree once at the end of the process. For example, consider the following:

var comments=customBlog.getComments('index'),
var c=comments.count;
var entry;
var commentDiv = document.createElement('div'),
document.body.appendChild(commentDiv);
for (var i=0;i<c;i++) {
  entry=document.createElement("p");
  entry.appendChild( document.createTextNode(comments[i]);
  commentDiv.appendChild( entry );
}

Consider the placement of the document.body.appendChild() method in the code. Because you add the new div element to the DOM before you add children to it, the document must be updated for each new paragraph added. However, you can speed up the routine considerably by moving the offending line to the end:

var comments=customBlog.getComments('index'),
var c=comments.count;
var entry;
var commentDiv = document.createElement('div'),
for (var i=0;i<c;i++) {
  entry=document.createElement("p");
  entry.appendChild( document.createTextNode(comments[i]);
  commentDiv.appendChild( entry );
}
document.body.appendChild(commentDiv);

With the restructured code, the document display only needs to be updated once instead of multiple times.

Combining document.write() Calls

Along the same line, you should avoid excessive document.write() calls. Each call is a performance hit. Therefore, a much better practice is to assemble a concatenated string variable first. For example, compare the following:

// Inefficient
document.write("<div class="row">");
document.write("<label class="cui">office</label>");
document.write("<a class="cuiServiceLink"
   target="_self" href="tel:(765) 555"1212">(765) 555"1212</a>");
document.write("</div>");
// More efficient
var s = '<div class="row">" + '<label class="cui">office</label>" +
"<a class="cuiServiceLink"
   target="_self" href="tel:(765) 555"1212">(765) 555"1212</a>" + '</div>";
document.write(s);

Using the Window Object

The window object is faster to use because Safari does not have to navigate the DOM to respond to your call. The last window reference in the following example is more efficient than the first three:

// Inefficient
var h=document.location.href;
var h=document.URL;
var h=location.href;
// More efficient
var h=window.location.href

LOCAL AND GLOBAL VARIABLES

One of the most important practices JavaScript coders should implement in their code is to use local variables and avoid global variables. When Safari processes a script, local variables are always looked for first in the local scope. If it can’t find a match, then it moves up the next level, and the next, and so on, until it hits the global scope. Consequently, global variables are the slowest in a lookup. For example, defining variable a at the global level in the following code is much more expensive than defining it as a local variable inside the for routine:

// Inefficient
var a=1;
function myFunction(){
  for(var i=0;i<10;i++) {
    var t = a+i;
    // do something with t
  }
}
//More efficient
function myFunction(){
  for(var i=0,a=1;i<10;i++) {
    var t = a+i;
    // do something with t
  }
}

DOT NOTATION AND PROPERTY LOOKUPS

Accessing objects and properties by dot notation is never efficient. Therefore, consider some alternatives.

Avoiding Nested Properties

Aim to keep the levels of dot hierarchy small. Nested properties, such as document.property.property.property, cause the biggest performance problems and you should avoid them or access them as few times as possible.

// Inefficient
m.n.o.p.doThis();
m.n.o.p.doThat();
// More efficient
var d = m.n.o.p;
d.doThis();
d.doThat();

Accessing a Named Object

If you access a named object, it is more efficient to use getElementById() rather than access it via dot notation. For example, compare the following:

// Inefficient
document.form1.addressLine1.value
// More efficient
document.getElementById( 'addressLine1" ).value;

Property Lookups Inside Loops

When accessing a property inside a loop, it is much better practice to cache the property reference first, and then access the variable inside the loop. For example, compare the following:

// Inefficient
for(i = 0; i <10; i++) {
var v = document.object.property(i);
var y = myCustonObject.property(i);
// do something
}
// More efficient
var p = document.object.property;
var cp = myCustonObject.property(i);
for(i = 0; i <10; i++) {
var v= p(i);
var y=cp(i);
// do something
}

Here’s another example of using the length property of an object in the condition of a for loop:

// Inefficient
for (i=0;i<myObject.length;i++) {
  // Do something
}
// More efficient
for (i=0,var j=myObject.length;i<j;i++) {
  // Do something
}

Similarly, if you are using arrays inside loops and using the length of the array as a conditional, you want to assign its length to a variable rather than evaluating at each pass. Check this out:

// Inefficient
myArray = new Array();
for (i=0;i<myArray.length;i++) {
  // Do something
}
// More efficient
myArray = new Array();
len = myArray.length;
for (i=0;i<len;i++) {
  // Do something
}

String Concatenation

Another traditional problem area in JavaScript is string concatenation. In general, you should try to avoid an excessive number of concatenations and an excessively large string that you are appending to. For example, suppose you are trying to construct a table in code and then write out the code to the document after you are finished. The stringTable() function in the following code is less efficient than the second function intermStringTable() because the latter uses an intermediate string variable row as a buffer in the for loop.

<html>
<script type="text/javascript" language="javascript">
function stringTable() {
  var start = new Date().getTime();
  var buf = "<table>";
  for (var i=0; i<10000;i++){
    buf += "<tr>";
    for (var j=0;j<40;j++){
      buf += "<td><i>" + "content" + "</i></td>";
    }
    buf += "</tr>";
  }
  buf += "</table>";
  var duration = new Date().getTime() — start;
  document.write( 'String concat method: ' + duration + '</br>");
}
function intermStringTable(){
  var start = new Date().getTime();
  var buf = "<table>";
  for (var i=0; i<10000;i++){
    var row = "<tr>";
    for (var j=0;j<40;j++){
      row += "<td><i>" + "content" + "</i></td>";
    }
    row += "</tr>";
    buf += row
  }
  buf += "</table>";
  var duration = new Date().getTime() — start;
  document.write("Intermediate concat method: ' + duration + '</br>");
}
</script>
<body>
</body>
<script type="text/javascript" language="javascript">
stringTable();
intermStringTable();
</script>
</html>

WHAT TO DO AND NOT TO DO

You want to be sure to avoid with statements, which slow down the processing of the related code block. In addition to the fact that with is inefficient, it has also been deprecated in the JavaScript standard. Second, avoid using eval() in your scripts. It is very expensive from a performance standpoint. Besides, you should be able to develop a more efficient solution rather than resorting to eval().

Comments add to readability and manageability, but be wise in using them. For example, use them minimally inside loop routines, functions, and arrays. Instead, place comments before or after a look structure to ensure greater efficiency.

// Inefficient
var a=0,c=100;
for (var i=0;i<c;i++) {
  // Assign d the value of the next div in the current document
  var d = document.getElementByTagName('div')[i];
  // Perform some math for a
  a=i*1.2;
  // Perform some math for b
  b=(a+i)/3;
}
// More efficient
// Assign val of d to 100 divs and perform y on them
// based on val of a and b.
var a=0,c=100;
for (var i=0;i<c;i++) {
  var d = document.getElementByTagName('div')[i];
  a=i*1.2;
  b=(a+i)/3;
}

EXERCISES

1. Is it important to minimize the number of external files you reference in your web app?

2. Does length of CSS style names and JavaScript variables make much of a difference?

3. Is it more efficient to reference an element in your JavaScript code using dot notation or getElementById()?

Answers to the Exercises can be found in Appendix A.

• WHAT YOU LEARNED IN THIS CHAPTER

TOPIC KEY CONCEPTS
Minimizing bandwidth Minimize the number of external files
Remove content that doesn’t need to be in your files (comments, extra white space, unused code/styles)
Shrink your images as much as possible without degrading image quality
Compressing your application Compress your JavaScript code using a compression library
Optimize JavaScript code Avoid nesting objects
Use getElementById() instead of dot notation
Limit the number of strings you concatenate
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.141.17