Monday, June 29, 2009

O3D update: New capabilities and expanded compatibility

We're happy to announce that today we shipped a substantial update to O3D, an API for creating rich 3D applications in a web browser. With today's release, we focused on addressing a theme we heard in the requests and feedback from the community: that O3D should run as well as possible on many different types of hardware. Toward that end, we're releasing two new additions: software rendering and feature requirements. If you've already installed the O3D plugin, you should receive these additions automatically.

Software rendering allows O3D to use the main processor to render 3D images if the machine running the app doesn't have supported graphics hardware. While the hardware O3D requires to run in hardware-accelerated mode is fairly modest by today's standards (a DirectX 9, Pixel Shader 2.0 capable graphics card), there are nonetheless PCs that don't meet these requirements, and we think it's important for web apps to run on all machines, regardless of hardware.

Because software rendering is significantly slower than hardware-accelerated rendering, we're also introducing a concept called "feature requirements" that will help minimize how often O3D will have to fall back to software rendering. Feature requirements allow developers to state upfront that their app will require certain hardware capabilities to render properly. If the machine running the app supports those features, O3D will run it fully hardware accelerated; if however, it is lacking any of the required capabilities, O3D will drop into a software rendered mode. Anecdotally, we found that this tiering allows 45 of our 48 samples to now run in hardware-accelerated mode with less capable graphics cards.

Finally, while it has nothing to do with extending hardware support, we're also adding a couple other goodies: a full-screen mode to make O3D apps more absorbing and a community gallery to feature cool demos that use O3D (like Infinite Journey, the first game developed outside Google using O3D). If you've developed an application or sample that would be useful to the O3D community, please be sure to submit it for our team to review for inclusion in the gallery using this form.

Henry Bridge

Thursday, June 25, 2009

Gmail for Mobile HTML5 Series : Cache Pattern For Offline HTML5 Web Applications

On April 7th, Google launched a new version of Gmail for mobile for iPhone and Android-powered devices. We shared the behind-the-scenes story through this blog and decided to share more of our learnings in a brief series of follow-up blog posts. This week, I'll talk about the cache pattern for building offline-capable web applications.

I recently gave a talk (preserved YouTube here) about the cache pattern and the Web Storage Portability Layer (WSPL) at Google I/O. It was exciting getting to give a talk at the Moscone Center as previously I had only ever been one of the audience members. The conference seemed to go by in a blur for me as I was sleep-deprived from getting the WSPL to "just good enough" to actually be released. (And some ofyou have already pointed out that I missed several bugs.) In my talk, I provided a general overview of the cache pattern and this post expands on the handling of hit determination and merging server and local changes.

The cache pattern is a design pattern for building an offline-capable web application. We implemented the cache pattern to make Gmail for Mobile tolerant of flaky wireless connections but the approach is generally applicable. Here's how it works. Consider a typical AJAX application. As shown in the diagram, we have a web application with a local model, view and controllers. The user interacts with theapplication and the controller dispatches XmlHttpRequests (XHRs for short) to the server. The server sends asynchronous requests to the application which it inserts into the model.

As shown in this next diagram, in the cache pattern, we insert a cache between the application and the server. Having done so, many requests that would otherwise require a round-trip to the network.

A software cache like this one shares a great deal conceptually with hardware caches. When designing the cache used in Gmail for mobile, we used this similarity to guide our design. For example, to keep our cache as simple as possible, we implemented a software equivalent to a write-through cache with early forwarding and LRU eviction. The cache pattern in general (and consequently our implementation) has four important data flows as shown in the diagram.

  • Cached content bound for the UI.
  • Changes made to the cache by the user in the UI. These need to be both reliably sent to the server and updated locally in the cache so that reads from the cache for UI updates show the state including user changes.
  • The changes recorded in the cache need to be sent upstream to the server as the network connection is available.
  • Changes made to the server (like email delivery in the case of Gmail) need to be merged into the contents of the cache.
As shown in the diagram we also need a place to actually write the data. We use the WSPL library to write a cache implementation portable across both Gears and HTML5 databases.

To actually implement these four data flows, we need to decide on a hit determination mechanism, a coherency strategy and a refresh approach.

Hit Determination

At its heart, a cache is a mapping from keys to values: the UI invokes the cache with a key and the cache returns the corresponding element. While this sounds pretty simple, there is an additional source of complexity if the application wants to provide the user with summary listings of some subset of all values available from the server. To provide this feature, the cache needs to contain not only "real" data values but additional "index" values that list the keys (and possibly user-visible summaries) for "data" values. For example, in Gmail for mobile, the cache stores conversations as its "real" data values and lists of conversations (such as the Inbox in Gmail for Mobile) as its "index" values. Keys for index values are computed specially to record what subset of the complete index is cached locally. For example, in Gmail for Mobile, while a user's Inbox may contain thousands of conversations, the cache might contain an index entry whose data values lists metadata for only conversations 1000 through 1100. Consequently, Gmail for Mobile's cache extends keys with the cached range so that a request for metadata for conversations 1101 through1110 would be considered a cache miss.

Coherency and Refresh

Perhaps the most complex aspect of the cache implementation is deciding how to get updated content from the server and how to merge server updates with changes made locally. A traditional hardware cache resolves this problem by only letting one processor modify its a cache at a time and have the memory broadcast any changes to all the other caches in the system. This approach cannot work here because the Gmail server can't connect to all of its clients and update their state. Instead, the approach we took for Gmail for Mobile was for the client device regularly poll the server for alterations.

Polling the server for changes such as new email or the archiving of email by the same user from a different device implies a mechanism for merging local changes with server side changes. As mentioned above, Gmail for Mobile is a write-through cache. By keeping all of the modifications to the cache in a separate queue until they have been acknowledged, they can be played back against updates delivered from the server so that the cache contains the merge of changes from the server and the local user. The following diagram shows the basic idea:


The green box in the diagram shows the contents of the cache's write buffer changing over time and the cloud corresponds to the requests in-flight to the server with time advancing from left to right in the diagram. The function names shown in the diagram are from the simplenotes.js
example file in the Web Storage Portability Layer distribution. Here, the user has applied some change [1] and the cache has written it to the write buffer and has then requested new content resulting in query [Q]. The cache prefixes the outstanding actions from the write buffer to the query. Action [1] is marked as needing a resend on some sort of network failure.

Later, the user makes change [2] to the UI which causes the cache to append it to the write buffer in the applyUIChange call. Later still, another query is made and so, the cache sends [1][2][Q] to the server. In the mean time, the user makes yet another change [3]. This is written to the write buffer. Once changes [1] and [2] are acknowledged by the server along with the new cache contents for query [Q], changes [1] and [2] are removed from the write buffer. However, to keep the cache's state reflecting the user's changes, change [3] is applied (again) over top of the result for [Q].

Simplifying the implementation of this reapplication stage is the most important benefit of implementing a write-through cache. By separating the changes from the state, it becomes much easier to reapply the changes to the cache once the server has delivered new content to the cache. As discussed in a previous post, the use of SQL triggers can greatly improve database performance. Whether updating or re-updating, triggers are a great way to make the application of changes to the cache much more efficient.

Cached Content To the UI

The first of the four data flows is delivering content to the UI is reasonably easy: query the cache for the desired content and when the query completes, forward the request to the UI. If you look at the getNoteList_ function from the simplenotes.js example code included in the WSPL distribution, you'll see that the delivering cached content to the UI has the following basic steps:
perform hit determination: deciding if the requested cache contents are actually in the cache.
  • create a database transaction, and while in the transaction
    • query the database for the desired key
    • accumulate the results
  • then outside of the transaction, return the result to the UI.
Changes From The UI

The second flow (applyUiChange) is recording changes made by the user to the write buffer. It has a very similar structure
  • create a database transaction, and while in the transacation
    • write the change to the write buffer
    • wait for a trigger to update the state of the cache.

Updates Bound For The Server

As discussed above, once the changes have been written to the write buffer, they still have to be sent to the server. This happens by prepending them to queries bound for the server. The fetchFromServer from the example is responsible for this. As might be familiar by now, the flow is

  • create a database transaction and while in the transaction
    • query the write buffer for all the entries that need to be sent to the server
    • accumulate the entries
  • then outside the transaction, send the combination of changes and query to the server

Changes From The Server

Finally, we need to merge the changes from the server into the cache as is done in the insertUpdate method from the example. Here the flow is as follows:

  • create a database transaction and while in the transaction
    • update the directory
    • write the new content into the cache
    • touch the changes in the write buffer that need to be re-applied to the cache
    • wait for the trigger to complete its update
  • then, outside of the transaction, send the response to the UI if it was satisfying a cache miss.
That's a brief intro to the cache architecture as found in Gmail for mobile. We're continuing to improve our implementation of this basic architecture to improve both the performance and robustness of Gmail for mobile. Please stay tuned for follow on blog posts.

Previous posts from Gmail for Mobile HTML5 Series
HTML5 and Webkit pave the way for mobile web applications
Using AppCache to launch offline - Part 1
Using AppCache to launch offline - Part 2
Using AppCache to launch offline - Part 3
A Common API for Web Storage
Suggestions for better performance

Robert Kroeger, Software Engineer, Google Mobile Team

Wednesday, June 24, 2009

AdSense for Mobile Applications Beta

Are you developing free iPhone or Android applications? With our new beta product - AdSense for Mobile Applications, you can monetize your mobile applications by showing contextually targeted ads and/or placement targeted ads alongside your application content. We provide you with iPhone and Android SDKs and example applications that request and display AdSense ads. Our SDKs also support DoubleClick ads.

You can show 320x50 text and image ads linked to HTML webpages in your application. These ads are targeted to the keywords that you send us in the AdSense (or DoubleClick) ad request. The keywords must be relevant to your application content. If your application content is loaded from a webpage that is customized for iPhones and Android handsets, then you can also send us the webpage URL for us to target ads. The ads may also be placement targeted which means an advertiser can specifically target to your application.

Our iPhone SDK is compatible with iPhone OS 3.0, and our Android SDK is compatible with Android 1.5 SDK. The SDKs include a library that can be linked in to your application which exposes methods to fetch and show ads. You must place a maximum of one ad per screen at the top or bottom (see the screenshot from the Backgrounds iPhone application). When a user clicks on the ad in your application, you can choose whether the user should view the advertiser's website in iPhone Safari or a full-screen UIWebView on the iPhone. For Android applications, our API defaults to opening the advertiser's website in the native browser.

To get started with monetizing your iPhone or Android application, sign up today on the AdSense for Mobile Applications website. We can't wait to have you join our beta network!


Tuesday, June 23, 2009

Let's make the web faster

From building data centers in different parts of the world to designing highly efficient user interfaces, we at Google always strive to make our services faster. We focus on speed as a key requirement in product and infrastructure development, because our research indicates that people prefer faster, more responsive apps. Over the years, through continuous experimentation, we've identified some performance best practices that we'd like to share with the web community on code.google.com/speed, a new site for web developers, with tutorials, tips and performance tools.

We are excited to discuss what we've learned about web performance with the Internet community. However, to optimize the speed of web applications and make browsing the web as fast as turning the pages of a magazine, we need to work together as a community, to tackle some larger challenges that keep the web slow and prevent it from delivering its full potential:
  • Many protocols that power the Internet and the web were developed when broadband and rich interactive web apps were in their infancy. Networks have become much faster in the past 20 years, and by collaborating to update protocols such as HTML and TCP/IP we can create a better web experience for everyone. A great example of the community working together is HTML5. With HTML5 features such as AppCache, developers are now able to write JavaScript-heavy web apps that run instantly and work and feel like desktop applications.

  • In the last decade, we have seen close to a 100x improvement in JavaScript speed. Browser developers and the communities around them need to maintain this recent focus on performance improvement in order for the browser to become the platform of choice for more feature-rich and computationally-complex applications.

  • Many websites can become faster with little effort, and collective attention to performance can speed up the entire web. Tools such as Yahoo!'s YSlow and our own recently launched Page Speed help web developers create faster, more responsive web apps. As a community, we need to invest further in developing a new generation of tools for performance measurement, diagnostics, and optimization that work at the click of a button.

  • While there are now more than 400 million broadband subscribers worldwide, broadband penetration is still relatively low in many areas of the world. Steps have been taken to bring the benefits of broadband to more people, such as the FCC's decision to open up the white spaces spectrum, for which the Internet community, including Google, was a strong champion. Bringing the benefits of cheap reliable broadband access around the world should be one of the primary goals of our industry.
To find out what Googlers think about making the web faster, see the video below. If you have ideas on how to speed up the web, please share them with the rest of the community. Let's all work together to make the web faster!





(Cross-posted on the Official Google Blog, and the Google Webmaster Central Blog)

Monday, June 22, 2009

Introducing the Virtual Keyboard API

Inability to input text in native language has been a problem for many non-latin script based languages. This may happen for many reasons. Sometimes, users do not have the keyboard layout for their native language installed in the system they happen to be using (for example, a tourist using an internet cafe in a foreign country). Sometimes, such a keyboard layout is not well developed or not widely available. It is worse for web developers because there is no way they can ensure that their users have access to this very basic input technology.

To address this issue, today, we added Virtual Keyboard API into the Google AJAX Language API. With this API, developers can help their users to input text, regardless if they have the native keyboard layout installed in their Operating Systems or not.


Pic 1: Russian Virtual Keyboard layout

Another advantage is the ability to provide a better user experience for multilingual web sites. For example, on a Russian/Thai bilingual dictionary editing web site, users would type in Russian in the header, and then see a Thai description. With the Virtual keyboard API, developers can load a Russian virtual keyboard layout and bind with all the Russian text fields, and load a Thai virtual keyboard layout and bind them to Thai fields. The Virtual Keyboard API then will automatically swap to the corresponding keyboard layout depending upon the user action.

Sometimes users may not be familiar with the key assignment of their keyboard layout. Virtual keyboard also shows the key assignment inside the page to allow users to input text by either pressing key or by clicking mouse on the virtual onscreen layout.

With this initial release, we are launching 5 language layouts. These are: Arabic, Hindi, Polish, Russian, and Thai.

We plan to roll out support for more keyboard layouts in the future. You can find more details by reading through the class reference and trying the Code Playground samples. Feedback is always welcome in our support forum and IRC channel.

Thursday, June 11, 2009

Google Technology User Groups

My favorite part about Google I/O is the dozens of interesting conversations with developers -- getting a first-hand look at the different things that they are doing with our technologies. That's the spirit of the Google Technology User Groups -- regular meetups where local developers can get together to network and discuss, demo, and hack on Google's many developer offerings.

From lightning talks in Mountain View, to App Engine hackathons in Tokyo, to lectures in Berlin, the GTUGs are a great place to meet fellow developers and learn (or teach) something new.

At Google I/O, there were many folks eager to bring the spirit of the conference back to their hometowns by starting up GTUGs of their own. Since the conference ended, our list of current GTUGs has grown to include this 'baby boomer' generation of chapters. The following are all new groups looking for members and starting to set up their first events.

If there's one near you, check it out! Let the organizers know you're interested; suggest topics for discussion and even offer to do a talk about your own experiences.

Europe

Paris GTUG - http://groups.google.com/group/paris-gtug
Hamburg GTUG - http://www.hamburg-gtug.org
GTUG Munich - http://gtug-muc.org
Istanbul GTUG - http://www.istanbul-gtug.org/
Polish GTUG - http://www.gtug.pl

North America

Tri-Valley California GTUG - http://groups.google.com/group/tv-gtug
Berkeley GTUG - http://www.meetup.com/Berkeley-GTUG/
San Diego GTUG - http://www.meetup.com/sd-gtug/
NYC GTUG - http://sites.google.com/site/nycgtug
New Jersey GTUG - http://nj-gtug.org/
Philly/Delaware GTUG - http://sites.google.com/site/phillygtug/
Boston GTUG - http://groups.google.com/group/boston-gtug
Denver GTUG - http://groups.google.com/group/denver-gtug
Twin Cities GTUG - tc-gtug.org
Austin GTUG - http://sites.google.com/site/austingtug/
Michigan GTUG - http://groups.google.com/group/mi-gtug
Utah GTUG - http://utahgtug.blogspot.com/
Laguna GTUG - www.laguna-gtug.org
Quebec GTUG - http://groups.google.com/group/gtug-quebec/?pli=1

South America
Chile GTUG - http://groups.google.com/group/gtug-cl
Argentina GTUG - http://groups.google.com/group/gtug-ar

Asia
Kuala Lumpur GTUG - http://sites.google.com/site/gtugkl/
Hyderabad GTUG - http://sites.google.com/site/hydgtug/

Also a big shout-out to our existing chapters:

Silicon Valley GTUG - http://www.meetup.com/sv-gtug (watch the organizers, Kevin and Van, talk about GTUGs at Google I/O)
Pune GTUG - http://pune-gtug.blogspot.com/
Chico GTUG http://www.chico-gtug.org
Berlin GTUG - http://www.berlin-gtug.org
Tokyo GTUG - http://tokyo-gtug.org/


View GTUGs in a larger map

Don't see a chapter near you? Start one! Join our GTUG managers mailing list. Other info at gtugs.org.

Wednesday, June 10, 2009

Google I/O Interactive Map: Now with videos + some Open Source goodness!

If you attended Google I/O 2009 a few weeks ago, you may have noticed a kiosk station on the 2nd and 3rd floors of Moscone West labelled 'Interactive Conference Map, powered by Google Maps'. The kiosk simply pointed to a JavaScript Maps API-based interactive map of the venue I created in my 20% time.

Now that all the I/O session videos and presentations are live, we took the opportunity to mash up the videos with our interactive conference map to provide developers with an alternate way to navigate through 80+ keynote and session videos, and bring the action at I/O to life virtually. For example, here are videos of sessions that took place in Room 1 (click the tabs for Wednesday and Thursday sessions). And here's where the keynote sessions took place. Check out where we filmed interviews with I/O sandbox developers on their apps, technical challenges and business best practices.


Now, hopefully you enjoyed using the map and are now thinking, "Cool, I want to do something like this for my next event!" (or your college campus, or such). If you are, then good news everyone, I've open sourced the interactive conference map and all relevant resources. Inside the project, you'll also find a how to article outlining the steps I went through to create the map.

If you attended I/O, then I hope you enjoyed it and had time to stop by the conference map kiosk! If not, no worries, just make sure to check out the open source project and see if you can use the code and/or techniques in your next mapping project!

Gmail for Mobile HTML5 Series: Suggestions for Better Performance

On April 7th, Google launched a new version of Gmail for mobile for iPhone and Android-powered devices. We shared the behind-the-scenes story through this blog and decided to share more of our learnings in a brief series of follow-up blog posts. This week, I'll talk about a few small things you can do to improve performance of your HTML5-based applications. Our focus here will be on performance bottlenecks related to the database and AppCache.

Optimizing Database Performance

There are hundreds of books written about optimizing SQL and database performance, so I won't bother to get into these details, but instead focus on things which are of particular interest for mobile HTML5 apps.

Problem: Creating and deleting tables is slow! It can take upwards of 200 ms to create or delete a table. This means a simple database schema with 10 tables can easily take 2-4 seconds (or more!) just to delete and recreate the tables. Since this often needs to be done at startup time, this really hurts your launch time.

Solution: Smart versioning and backwards compatible schema changes (whenever possible). A simple way of doing this is to have a VERSION table with a single row that includes the version number (e.g., 1.0). For backwards-compatible version changes, just update the number after the decimal (e.g., 1.1) and apply any updates to the schema. For changes that aren't backwards compatible, update the number before the decimal (e.g., 2.0) at which point you can drop all the tables and recreate them all. With a reasonable schema design to begin with, it should be very rare that a schema change is not backwards compatible and even if this happens every month or so, users should get to use your application 20, 30 even 100 times before they hit this startup delay again. If your schema changes very infrequently, a simple 1, 2, 3 versioning scheme will probably work fine; just make sure to only recreate the database when the version changes!

Problem: Queries are slow! Queries are faster than creates and updates, but they can still take 100ms-150ms to execute. It's not uncommon for traditional applications to execute dozens or even hundreds of queries at startup – on mobile this is not an option.

Solution: Defer and/or combine queries. Any queries that can be deferred from startup (or at any other significant point in the application) should be deferred until the data is absolutely needed. Adding 2-3 more queries on a user-driven operation can turn an action from appearing instantaneous to feeling unresponsive. Any queries that are performed at startup should be optimized to require as few hits to the database as possible. For example, if you're storing data about books and magazines, you could use the following two queries to get all the authors along with the number of books and magazine articles they've writen:

SELECT Author, COUNT(*) as NumArticles
FROM Magazines
GROUP BY Author
ORDER BY NumArticles;

SELECT Author, COUNT(*) as NumBooks
FROM Books
GROUP BY Author
ORDER BY NumBooks;


This will work fine, but the additional query will generally cost you about 100-200 ms over a different (albeit less pretty) query like:

SELECT Author, NumPublications, PubType
FROM (
SELECT Author, COUNT(*) as NumPublications, 'Magazine' as PubType, 0 as SortIndex
FROM Magazines
GROUP BY Author
UNION
SELECT Author, COUNT(*) as NumPublications, 'Book' as PubType, 1 as SortIndex
FROM Books
GROUP BY Author
)
ORDER BY SortIndex, NumPublications;

This will return all the entries we want, with the magazine entries first in increasing order of number of articles, followed by the book entries, in increasing order of the number of books. This is a toy example and there are clearly other ways of improving this, such as merging the Magazines and Books tables, but this type of scenario shows up all the time. There's always a trade-off between simplicity and speed when dealing with databases, but in the case of HTML5 on mobile, this trade-off is even more important.

Problem: Multiple updates is slow!

Solution: Use Triggers whenever possible. When the result of a database update requires updating other rows in the database, try to do it via SQL triggers. For example, let's say you have a table called Books listing all the books you own and another called Authors storing the names of all the authors of books you own. If you give a book away, you'll want to remove it from the Books table. However, if this was the only book you owned by that author, you would also want to remove the author from the Authors table. This can be done with two UPDATE statements, but a "better" way is to write a trigger that automatically deletes the author from the Authors table when the last book by this author is removed. This will execute faster and because triggers happen asynchronously in the background, it will have less of an impact on the UI than executing two statements. Here's an example of a simple trigger for this case:

CREATE TRIGGER IF NOT EXISTS RemoveAuthor
AFTER DELETE ON Books
BEGIN
DELETE FROM Authors
WHERE Author NOT IN
(SELECT Author
FROM Books);
END;
We'll get into more detail on triggers and how to use them in another performance post to come.

Optimizing AppCache Performance

Problem: Logging in is slow!

Solution: Avoid redirects to the login page. App-Cache is great because it can launch the application without needing to hit the network, which makes it much faster and allows you to launch offline. One problem you might encounter though, is that the application will launch and then you'll need to hit the network to get some data for the current user. At this point you'll have to check that the user is authenticated and it might turn out that they're not (e.g., their cookies might have expired or have been deleted). One option is to redirect the user to a login page somewhere, allow him to authenticate and then redirect him back to the application. Regardless of whether or not the login page is listed in the manifest, when it redirects back to your application, the entire application will reload. A nicer approach is for the application itself to display an authentication interface which sends the credentials and does the authentication seamlessly in the background. This will avoid any additional reloads of the application and makes everything feel faster and better integrated.

Problem: AppCache reloading causes my app to be slow!

Solution: List as few URLs in the manifest as possible. In a series of posts on code.google.com, we talked about the HTML5 AppCache manifest file. An important aspect of the manifest file is that when the version gets updated, all the URLs listed in the file are fetched again. This happens in the background while the user is using the application, but opening all these network connections and transferring all that data can cause the application to slow down considerably during this process. Try to setup your application so that all the resources can be fetched from as few URLs as possible to speed up the manifest download and minimize this effect. Of course you could also just never update your manifest version, but what's the point of having rapid development if you never make any changes?


That's a brief intro to some performance considerations when developing HTML5 applications. These are all issues that we ran into ourselves and have either fixed or are in the process of fixing in our application. I hope this helps you to avoid some of the issues we ran into and makes your application blazing fast!

We plan to write several more performance related posts in the future, but for now stay tuned for next post where we'll discuss the cache pattern for building offline capable web applications.



Previous posts from Gmail for Mobile HTML5 Series
HTML5 and Webkit pave the way for mobile web applications
Using AppCache to launch offline - Part 1
Using AppCache to launch offline - Part 2
Using AppCache to launch offline - Part 3
A Common API for Web Storage

Another Round of Deprecation Policies for Labs Graduates

We recently published deprecation policies for a number of APIs that graduated from Google Code Labs. They state how long we'll support each version from when it's deprecated or a newer version is introduced. It will be 3 years for most, but the time period varies a bit from product to product.
We still need to update the terms for a couple remaining graduates, but should have them all done within the next couple weeks.

Tuesday, June 09, 2009

Nicholas C. Zakas: Speed Up Your JavaScript

Nicholas C. Zakas delivers the seventh Web Exponents tech talk at Google. Nicholas is a JavaScript guru and author working at Yahoo!. Most recently we worked together on my next book, Even Faster Web Sites. Nicholas contributed the chapter on Writing Efficient JavaScript, containing much of the sage advice found in this talk. Check out his slides and watch the video.



Nicholas starts by asserting that users have a greater expectation that sites will be fast. Web developers need to do most of the heavy lifting to meet these expectations. Much of the slowness in today's web sites comes from JavaScript. In this talk, Nicholas gives advice in four main areas: scope management, data access, loops, and DOM.

Scope Management: When a symbol is accessed, the JavaScript engine has to walk the scope chain to find that symbol. The scope chain starts with local variables, and ends with global variables. Using more local variables and fewer global variables results in better performance. One way to move in this direction is to store a global as a local variable when it's referenced multiple times within a function. Avoiding with also helps, because that adds more layers to the scope chain. And make sure to use var when declaring local variables, otherwise they'll end up in the global space which means longer access times.

Data Access: In JavaScript, data is accessed four ways: as literals, variables, object properties, and array items. Literals and variables are the fastest to access, although the relative performance can vary across browsers. Similar to global variables, performance can be improved by creating local variables to hold object properties and array items that are referenced multiple times. Also, keep in mind that deeper object property and array item lookup (e.g., obj.name1.name2.name3) is slower.

Loops: Nicholas points out that for-in and for each loops should generally be avoided. Although they provide convenience, they perform poorly. The choices when it comes to loops are for, do-while, and while. All three perform about the same. The key to loops is optimizing what is performed at each iteration in the loop, and the number of iterations, especially paying attention to the previous two performance recommendations. The classic example here is storing an array's length as a local variable, as opposed to querying the array's length property on each iteration through a loop.

DOM: One of the primary areas for optimizing your web application's interaction with the DOM is how you handle HTMLCollection objects: document.images, document.forms, etc., as well as the results of calling getElementsByTagName() and getElementsByClassName(). As noted in the HTML spec, HTMLCollections "are assumed to be live meaning that they are automatically updated when the underlying document is changed." Any idea how long this code takes to execute?

var divs = document.getElementsByTagName("div");
for (var i=0; i < divs.length; i++) {
var div = document.createElement("div");
document.body.appendChild(div);
}

This code results in an infinite loop! Each time a div is appended to the document, the divs array is updated, incrementing the length so that the termination condition is never reached. It's best to think of HTMLCollections as live queries instead of arrays. Minimizing the number of times you access HTMLCollection properties (hint: copy length to a local variable) is a win. It can also be faster to copy the HTMLCollection into a regular array when the contents are accessed frequently (see the slides for a code sample).

Another area for improving DOM performance is reflow - when the browser computes the page's layout. This happens more frequently than you might think, especially for web applications with heavy use of DHTML. If you have code that makes significant layout changes, consider making the changes within a DocumentFragment or setting the className property to alter styles.

There is hope for a faster web as browsers come equipped with JIT compilers and native code generation. But the legacy of previous, slower browsers will be with us for quite a while longer. So hang in there. With evangelists like Nicholas in the lead, it's still possible to find your way to a fast, efficient web page.


Check out other blog posts and videos in the Web Exponents speaker series: