Wednesday, February 8, 2012

Chrome Fast for Android

The Chrome Beta for Android is solid Chrome. That means it inherits my favorite speed features from its older desktop siblings. Some of them are novel to mobile browsers. For all of them, the absolute performance benefit is usually greater than on more powerful desktops.

In no particular order, here are ten I'm most excited to see together in the mobile world.

  1. Remote debugging Probably the most important thing a browser can do to make the web fast is to provide developer tools which make it easy for web developers to build fast sites. While there have been some cutting edge projects to bring parts of DevTools to mobile, the full suite hasn't been available until now. Use the Timeline, Profiles and Network panels to find the trickiest bottlenecks.
  2. SPDY Also supported in the stock Android Browser on Honeycomb systems and newer, SPDY significantly reduces the number of costly RTTs that are incurred during a web page load. Keep in mind it only works with servers that speak SPDY. Apache users may be interested in following the progress of mod_spdy.
  3. Hardware accelerated graphics Getting this right was one of the largest efforts in bringing Chrome to Android. As a result, scrolling is buttery smooth on most reasonably efficient web sites. If your site isn't as smooth as you want it to be, take a profile in the Timeline panel to see where time is being spent. Often there are patterns for avoiding layouts or style recalculations to get back into the fast zone. A notable exception is CSS animations. While Chrome's framerate is similar to other mobile browsers, they aren't nearly as smooth as they need to be. Improving them is an area of focus at the moment.
  4. V8 Crankshaft Incorporating the largest performance improvement in V8's history allows a mobile browser to hold its own against even JavaScript heavy web sites designed for the desktop.
  5. Navigation Timing Also newly available in the stock Android Browser on Ice Cream Sandwich, support for the Navigation Timing API allows developers to understand real users' page load times. This further increases the coverage of the API which is already supported in the lastest versions of desktop Chrome, Firefox and IE.
  6. Large persistent cache As brought to note by Guy Podjarny, most mobile browsers have very tiny disk caches. Chrome's logic is based on the amount of disk space available, which means devices with plenty of free space are able to have significantly larger caches.
  7. requestAnimationFrame Support for this API is critical for building efficient JavaScript animations. UPDATE: Henri Sivonen points out that this was unclear. In Chrome, this API is vendor prefixed as webkitRequestAnimationFrame while it is being standardized.
  8. Preloading Searches in the omnibox trigger the preloading of high confidence results. In many cases this results in a near instant page load. By default this is enabled only when on WiFi, and that is customizable.
  9. SSL FalseStart This relatively recent improvement in the Chrome network stack avoids a costly RTT during the establishment of SSL connections. The commonly high RTT on mobile networks and increasing use of HTTPS on the web make this a nice win.
  10. HTML5 APIs Support for newer HTML5 APIs like Web Sockets, Web Workers and Indexed Database provide the building blocks for building high performance web apps.

Getting Chrome running on Android is only the first step. Now the really fun stuff can begin.

Saturday, December 17, 2011

Beyond web developer tools: strace

[cross-posted from 2011 Performance Calendar]

Rich developer tools are available for all modern web browsers. They are typically easy to use and can provide all of the information necessary to optimize web pages. It is rare to need to go beyond the unified networking/scripting/rendering view of the Web Inspector's Timeline panel.

But they aren't always perfect: a tool may be missing information, may disagree with another tool, or may just be incorrect. For instance, a recent bug occasionally caused two Navigation Timing metrics to be incorrect in Chrome (and the Inspector).

When these rare situations arise, great engineers are able to go beyond a browser's developer tools to find out exactly what the browser is telling the operating system to do. On Linux, this source of ultimate truth can be found using strace. This tool can trace each system call made by a browser. Since every network and file access entails a system call, and this is where browsers spend a lot of their time, it is perfect for debugging many types of browser performance issues.

What about other platforms?

In this post, I introduce strace because the syntax is clean and no setup is required. But most systems have an equivalent tool for tracing system calls. Mobile developers will be happy to hear that strace is fully supported by Android. OS X users will find dtrace offers more powerful functionality at the expense of less intuitive syntax (unfortunately not ported to iOS). Finally, Event Tracing for Windows (ETW), while harder to setup, supports a friendly GUI.

Getting started

To use it: open a terminal and invoke strace at the command prompt. This invocation prints all system calls while starting Google Chrome to google.com:

$ strace -f -ttt -T google-chrome http://www.google.com/

I've added -f to follow forks, -ttt to print the timestamp of each call and -T to print the duration of each call.

Zeroing in

If you run the command above, you'll probably be overwhelmed by the amount of stuff going on in a modern web browser. To filter down to something interesting, try using the -e argument. For examining only file or network access, try -e trace=file or -e trace=network. The man page has many more examples.

An example: local storage

As a concrete example, let's trace local storage performance in Chrome. First I opened a local storage quota test page. Then I retrieved the Chrome browser processes' ID from Chrome's task manager (Wrench > Tools > Task Manager) and attached strace to that process using the -p switch.

$ strace -f -T -p <process id> -e trace=open,read,write

The output shows the timestamps, arguments and return value of every open, read, and write system call. The man page for each call explains the arguments and return values. The first call of interest to us is this open:

open("/home/tonyg/.config/google-chrome/Default/Local Storage/http_arty.name_0.localstorage-journal", O_RDWR|O_CREAT, 0640) = 114 <0.000391>

This shows us that Chrome has opened this file for reading and writing (and possibly created it). The name of the file is a big clue that this is where local storage is saved for arty's web page. The return value, 114, is the file descriptor which will identify it in later reads and writes. Now we can look for read and write calls which operate on fd 114, for example:

write(114, "\0\0\00020\0001\0002\0003\0004\0005\0006\0007\0008\0009\0000\0001\0002\0003\0"..., 1024 <unfinished ...>
<... write resumed> ) = 1024 <0.425476>

These two lines show a 1,024 byte write of the data beginning with the string above to the local storage file (114). This write happened to take 425ms. Note that the call is split into two lines with possibly others in between because another thread preempted it. This is common for slower calls like this.

We've only scratched the surface

There are options for dumping the full data read/written from the network or filesystem. Running with -c displays aggregate statistic about the time spent in the most common calls. I've also found that some practical python scripting can quickly parse these traces into a variety of useful formats.

This brief introduction hardly does this tool justice. I merely hope it provides the courage to explore deeper into the stack the next time you run into a tricky performance problem.

Sunday, August 7, 2011

Finding memory leaks

Over lunch last week Mikhail Naganov (creator of the DevTools Heap Profiler) and I were discussing how invaluable it has been to have the same insight into JavaScript memory usage that we have into applications written in languages like C++ or Java. But the heap profiler doesn't seem to get as much attention from developers as I think it deserves. There could be two explanations: either leaking memory isn't a big problem for web sites or there is a problem but developers aren't aware of it.

Are memory leaks a problem?

For traditional pages where the user is encouraged to navigate from page to page, memory leaks should almost never be a problem. However, for any page that encourages interaction, memory management must be considered. Most realize that ultimately if too much memory is consumed the page will be killed, forcing the user to reload it. However, even before all memory is exhausted performance problems arise:

  • A large JavaScript heap means garbage collections may take longer.
  • Greater system memory pressure means fewer things can be cached (both in the browser and the OS).
  • The OS may start paging or thrashing which can make the whole system feel sluggish.
These problems are of course exacerbated on mobile devices which have less RAM.

A real world walkthrough

So, in order to demonstrate this is a real world problem and how easily the heap profiler can diagnose it, I set out to find a memory leak in the wild. A peak at the task manager (Wrench > Tools > Task Manager) for my open tabs showed a good candidate for investigation: Facebook is consuming 410MB!!


Pinpoint the leaky action

The first step in finding a memory leak is to isolate the action that leaks. So I loaded facebook.com in a new tab. The fresh instance used only 49MB -- another indicator the 410MB might have been due to a leak. To observe memory use over time, I opened the Inspector's Timeline panel, selected the Memory tab and pressed the record button. At rest, the page displays a typical pattern of allocation and garbage collection. This is not a leak.


While keeping an eye on the graph, I began navigating around the site. I eventually noticed that each time I clicked the Events link on the left side, memory usage would rise but never be collected. This is how the usage grows as I repeatedly click the link. A quintessential leak.


As an aside, this leak isn't a browser bug. The OS task manager shows similar memory growth when performing the same action in Firefox.

Find the leaked memory

Now that we know we have a leak, the obvious next question is what is leaking. The heap profiler's ability to compare heap snapshots is the perfect tool to answer it. To use it, I reloaded a new instance and took a snapshot by clicking the heap snapshot button at the bottom of the Profiles panel. Next, I performed the leaky action a prime number of times in hopes that it might be easy to spot. So I clicked Events 13 times and immediately took a second snapshot. To compare before and after, I highlighted the second snapshot and selected Comparison view.


The comparison view displays the difference between any two snapshots. I sorted by delta to look for any objects that grew by the same number of times I clicked: 13. Sure enough, there were 13 more UIPagelets on the heap after my clicks than before.


Expanding the UIPagelet shows us each instance. Let's look at the first.


Each instance has an _element property that points to a DOM node. Expanding that node, we can see that it is part of a detached DOM tree of 136 nodes. This means that 136 nodes are no longer visible in the page, but are being held alive by a JavaScript reference. There are legitimate reasons to do this, but it is also easy and common to do it by accident.


Note that all memory statistics reported by the tool reflect only the memory allocated in the JavaScript heap. This does not include native memory used by the DOM objects. So we cannot readily determine how much memory those 136 nodes are using. It all depends on their content -- for example leaking images can burn through memory very quickly.

Determine what prevents collection

After finding the leaked memory the last question is what is preventing it from being collected. To answer this we simply highlight any node and the retaining path will be shown (I typically change it to show paths to window objects instead of paths to GC roots). Here we see a very simple path. The UIPagelets are stored in a __UIControllerRegistry object.


At first I wondered if this might intentionally keep DOM nodes alive as a cache. However, that doesn't seem to be the case. A search of the source JS shows several places where items are added to the __UIControllerRegistry, but I couldn't find anywhere where they are cleaned up. So this appears to be a case where retaining the DOM nodes is purely accidental. The fix is to remove references to these nodes so they may be collected.

Takeaway

The point of the post is not that facebook has a leak. Facebook is an extremely well engineered site and large apps all deal with memory leaks from time to time. The point is to demonstrate how readily leaks can be diagnosed even with no knowledge of the source.

For anyone with an interactive web site, I highly recommend using your site for a few minutes with the memory timeline enabled to watch for any suspicious growth. If you have to solve any issues, the manual has excellent tutorials.