Wednesday, June 13, 2012

A better timer for JavaScript

Browser performance guru, Nat Duca, has introduced high resolution timing to JavaScript. Ready to try in Chrome 20, the experimental window.performance.webkitNow() interface provides microsecond resolution and guarantees not to flow backward (monotonically nondecreasing).

Microseconds matter

Until now, creating a new Date object and querying its time was the only way to measure elapsed time on the web. Depending on the browser and OS, Date's resolution can be as low as 15 milliseconds. One millisecond at best. As John Resig brought to note, that isn't sufficient for benchmarking.

Mono-what-ic?

Perhaps less often considered is that Date, based on system time, isn't ideal for real user monitoring either. Most systems run a daemon which regularly synchronizes the time. It is common for the clock to be tweaked a few milliseconds every 15-20 minutes. At that rate about 1% of 10 second intervals measured would be inaccurate.

Try it out

Like Date, now returns a number in milliseconds. Unlike Date, now's value is:

  • a double with microseconds in the fractional
  • relative to the navigationStart of the page rather than to the UNIX epoch
  • not skewed when the system time changes

If you're using Date for any performance measurement, add this shim to upgrade to performance.now() with a fall back to Date for older browsers.

window.performance = window.performance || {};
performance.now = (function() {
  return performance.now       ||
         performance.mozNow    ||
         performance.msNow     ||
         performance.oNow      ||
         performance.webkitNow ||
         function() { return new Date().getTime(); };
})();

Monday, April 23, 2012

Optimizing with the timeline panel

"I know I should look at Chrome's Timeline panel, but what do I do with it?" was roughly the opening line in two recent web performance meetings. In both we found nice speedups using the same mantra: "Don't ask how to make an operation faster, ask why it is done at all."

To ward off a third meeting, I decided to write about it. This is best demonstrated with a real world example, and it happens that there's a perfect one on Wikipedia.

Record a trace

To follow along at home, head over to your favorite lengthy Wikipedia article—mine is List of common misconceptions. Open the Inspector's Timeline panel, click the black circle record button at the bottom, refresh the page and finally stop recording after it loads. You now have an initially daunting trace of all the major steps of the page load.

Orient yourself

The top timescale marks the document's DOMContentLoaded event with a blue line and the window's load event with red. In the screenshot above, I dragged the righthand slider in to the red line to zoom in on only the events which happened during the page load.

Finally notice the event coloring scheme: loading in blue, scripting in yellow and rendering in purple. In the remainder of the screenshots I've unchecked the blue box to hide loading events—perhaps a topic for another day.

Is JavaScript slow?

In the above screenshot there are three JavaScript executions which look long enough to investigate. Hover on one to see the details.

This one took 405ms ("Duration"). The three together add up to 1.25 seconds or about a quarter of this 4.8 second page load. Alright, JavaScript looks slow. Should we hunt for loops to unroll? Shame! What's happening here is much more typical. Only 29ms ("Self Time") are spent actually executing this JavaScript and the other 372ms (purple "Aggregated Time") are spent on rendering operations triggered by the JavaScript.

Examine rendering operations

Expand the drop down to see the rendering operations: predominantly 4 style recalculations, each about 90ms. The other two scripts have very similar breakdowns (not shown).

Now it may seem the way to speed this up is to optimize style recalculation time. Should we scour for descendent selectors? Not just yet. For both layouts and style recalculations, first ask whether the operation is necessary at all. It is usually possible to write a given script such that it triggers a maximum of one layout and one recalculation. So this is a red flag. To figure out what caused these recalculations, hover to see the responsible JavaScript call stack.

It turns out that each of the three major script executions are dominated by style recalculations which all have the addInlineCSS method on their callstack. Click the links to see the source. In the source view, the button with the two curly brackets formats the minified JavaScript for human readability.

The fix

Within the execute method the culprit steps into the light: a loop that appends style elements to the document one by one. The browser does a full recalculation of all styles on the page each time this happens.

Fixing this is straightforward. Before appending to the DOM, either concatenate the CSS strings into a single style element or else coalesce the style elements into a DocumentFragment. This trace suggests that could chop almost a second off the page load.

Conclusion

I'd love to see this example fixed (hopefully in WebKit), but that's not the point of this post. There are various well known anti-patterns which can trigger wasteful rendering operations. They are easy to write and often not slow enough to worry about. Hopefully you've taken away the courage to open the timeline and diagnose the ones that do need to be optimized. Happy tracing!

Thursday, February 16, 2012

Thou shalt not kill

The recent beta release of Chrome on Android marks a great time to look at what happens when web applications consume too much memory: something gets killed.

Previously I've blogged about the importance for web developers to mind their memory usage. On powerful desktop machines enjoyed by many developers, this can seem like an academic concern. But on mobile devices the limitation is all too real. Case in point: I compile with 24G of RAM and deploy to a Nexus S with 512M, and of that, only two-thirds (about 342M) is available for the system to use.

Here's what happens as a web app approaches that 342M limit.

1. Android apps are killed

The Android system has a wonderful design in which the user doesn't need to be concerned with which apps are open at any given time. The system has the prerogative to pause and resume background apps based on available resources. So, the first thing that happens as a web app's memory usage grows is that background applications are gracefully paused and killed. This is normal operation which happens constantly, however, it does mean that users wait longer when switching to paused apps as they have to be restarted and resumed.

2. Chrome tabs are killed

Once the system has given Chrome as much memory as it can afford, it sends notifications as usage approaches the limit. When Chrome notices it is using too much memory or receives one of these notifications, it has no choice but to kill background tabs. This is a little less transparent to users because when the user switches back to a killed tab, instead of the usual near instantaneous switch, the page must be reloaded from cache or network.

3. Caches are cleared

Up until this point, memory pressure has caused some performance problems for the user, but the foreground content hasn't suffered much. However, after all background tabs have been killed, Chrome's only remaining recourse is to clear memory caches and perform additional V8 garbage collections. This begins to cut into the application's interactive performance (scrolling, animations, JavaScript responsiveness, etc). It is often a short lived fix as these caches can quickly refill. A high performing web application should never allow memory usage get this high.

4. Suicide

Finally, when the system can allocate memory no more, it kills Chrome's renderer process in which the web page lives. Many mobile browsers would crash entirely, but due to Chrome's unique multiprocess architecture, the browser keeps running and displays the "Aw, Snap!" page. The user, left frustrated, is free to quickly move on to a better performing web page.

Living a long life

Possibly my favorite feature of Chrome on Android is that the entire developer tools suite works through remote debugging. This makes it easy to keep an eye on memory usage and diagnose memory leaks when they do occur.

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.

Sunday, May 29, 2011

How a web page loads

The major web browsers load web pages in basically the same way. This process is known as parsing and is described by the HTML5 specification. A high-level understanding of this process is critical to writing web pages that load efficiently.

Parsing overview

As chunks of the HTML source become available from the network (or cache, filesystem, etc), they are streamed to the HTML parser. Next, in a process known as tokenization, the parser iterates through the source generating a token for (most notably) each start tag, end tag and character outside of a tag.

For example the input source <b>hello</b> yields 7 tokens:

start-tag { name: b }
character { data: h }
character { data: e }
character { data: l }
character { data: l }
character { data: o }
end-tag { name: b }

After each token is generated it is serially passed to the next major subsystem: the tree builder. The tree builder dynamically modifies the Document's DOM tree to reflect the new token.

The 7 input tokens above yield the following DOM tree:

<html>
  <head>
  <body>
    <b>
      "hello"

Fetching subresources

A frequent operation performed by the tree builder is creating a new HTML element and inserting it into the Document. It is at the point of insertion that HTML elements which load subresources begin fetching the subresource.

Running scripts

This parsing algorithm seems to translate HTML source into a DOM tree as efficiently as possible. That is, except for one wrinkle: scripts. When the tree builder encounters an end-tag token for a script, it must serially execute the script before parsing can continue (unless the associated script start-tag has the defer or async attribute).

There are two significant preconditions which must be fulfilled before a script can execute:

  1. If the script is external its source must be fully downloaded.
  2. For any script, all stylesheets in the document must be fully downloaded.

This means often the parser must idly wait while scripts and stylesheets are downloaded.

Why must parsing halt?
Well, a script may document.write something which affects further parsing or it may query something about the DOM which would yield incorrect results if parsing had continued (for instance the number of image elements in the DOM).

Why wait for stylesheets?

A script may expect to access the CSSOM directly or it may query an attribute of a DOM node which depends on the stylesheet (for example, how wide is a certain <table>).

Is it inefficient to block parsing?

Yes. Subresource download times often have a large constant factor limited by round trip time. This means it is faster to download two resources in parallel than to download the same two in serial. More obviously, the browser is also free to do CPU work while waiting on the network. For these reasons it is critical to efficient loading of a web page that subresource fetches are initiated as soon as possible. When parsing is blocked, the tree builder is not able to insert subsequent elements into the DOM, and thus subsequent subresource downloads are not initiated even if the HTML source which includes them is already available to the parser.

Mitigating blocking

As I've blogged previously, when the parser becomes blocked WebKit will run a lightweight parser known as the preload scanner. It mitigates the blocking problem by scanning ahead and fetching certain subresource that may be required. Other browsers employ similar techniques.

It is important to note that even with preload scanning, parsing is still blocked. Nodes cannot be added to the DOM tree. Although I haven't covered how a DOM tree becomes a render tree, layout or painting, it should be obvious that before a node is in the DOM tree it cannot be painted to the screen.

Finishing parsing

After the entire source has been parsed, first all deferred scripts will be executed (waiting for their source and all pending stylesheets to download). Their completion triggers the DOMContentLoaded event to be fired. Next, the parser will wait for any pending async scripts to finish loading and executing. Finally, once all subresources have finished downloading, the window's load event will be fired and parsing is complete.

Takeaway

With this understanding, it becomes clear how important it is to carefully consider where and how stylesheets and scripts are included in the document. Those decisions can have a significant impact on the efficiency of the page load.

Sunday, May 15, 2011

List of ways HTML can download a resource

Recently two different projects required compiling a list of ways to trigger a download through HTML: Resource Timing and Preload Scanner optimization.

There's no centralized list in the WebKit source nor did a web search turn one up. So in hopes it may be useful to others, here's what I was able to come up with. Please let me know what I forgot (note that ways to download through CSS, JS, SVG and plugins are intentionally omitted).

  • <applet archive>
  • <audio src>
  • <body background>
  • <embed src>
  • <frame src>
  • <html manifest>
  • <iframe src>
  • <img src>
  • <input type=image src>
  • <link href>
  • <object data>
  • <script src>
  • <source src>
  • <track src>
  • <video poster>
  • <video src>

It might be interesting to compare the performance characteristics of downloads by resource type across browsers. For instance download priority, memory cacheability, parsing blocking and preload scan detection will vary.

Sunday, March 27, 2011

How (not) to trigger a layout in WebKit

As most web developers are aware, a significant amount of a script's running time may be spent performing DOM operations triggered by the script rather than executing the JS byte code itself. One such potentially costly operation is layout (aka reflow) -- the process of constructing a render tree from a DOM tree. The larger and more complex the DOM, the more expensive this operation may be.

An important technique for keeping a page snappy is to batch methods that manipulate the DOM separately from those that query the state. For example:

// Suboptimal, causes layout twice.
var newWidth = aDiv.offsetWidth + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.height = newHeight + 'px'; // Write

// Better, only one layout.
var newWidth = aDiv.offsetWidth + 10; // Read
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
aDiv.style.height = newHeight + 'px'; // Write

Stoyan Stefanov's tome on repaint, relayout and restyle provides an excellent explanation of the topic.

This often leaves developers asking the question: What triggers layout? Last week Dimitri Glazkov answered this question with this codesearch link. Trying to understand it better myself, I went through and translated it into a list of properties and methods. Here they are:

Element

clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth

Frame, Image

height, width

Range

getBoundingClientRect(), getClientRects()

SVGLocatable

computeCTM(), getBBox()

SVGTextContent

getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()

SVGUse

instanceRoot

window

getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY, webkitConvertPointFromNodeToPage(), webkitConvertPointFromPageToNode()

This list is almost certainly not complete, but it is a good start. The best way to check for over-layout is to watch for the purple layout bars in the Timeline panel of Chrome or Safari's Inspector.

Saturday, February 5, 2011

Chrome's 10 Caches

While defining the set of page load time metrics that we think are most important for benchmarking, Mike Belshe, James Simonsen and I went through a seemingly simple exercise: enumerate the ways in which Chrome caches data. The resulting list was interesting enough to me that I thought it worthwhile to share.

When most people think of "the browser's cache" they envision a single map of HTTP requests to HTTP responses on disk (and perhaps partially in memory). This cache may arguably have the most impact on page load times, but to get to a truly stable benchmark, we identified 10 caches that need to be considered. An understanding of the various caches is also useful to web page optimization experts who seek to maximize cache hits.

  1. HTTP disk cache

    Stores HTTP responses on disk as long as their headers permit caching. Lookups are usually significantly cheaper than fetching over the network, but they are not free as a single disk seek might take 10-15ms and that doesn't include the time to read the data from disk.

    The maximum size of the cache is calculated as a percentage of available disk space. The contents can be viewed at chrome://net-internals/#httpCache. It can be cleared manually at chrome://settings/advanced or programmatically by calling chrome.benchmarking.clearCache() when Chrome is run with the --enable-benchmarking flag set. Note that for incognito windows this cache actually resides in memory. source

  2. HTTP memory cache

    Similar to the HTTP disk cache, but entirely unrelated in code. Lookups in this cache are fast enough that they may be thought of as "free."

    This cache is limited to 32 megabytes, however, when the system is not under memory pressure the effective limit may be higher due to its use of purgeable memory. Conversely, when multiple tabs are open, the limit may be divided among the tabs. It is cleared in the same manner as the HTTP disk cache. source

  3. DNS host cache

    Caches up to 100 DNS resolutions for up to 1 minute each. It is somewhat unfortunate that this cache needs to exist in Chrome, but OS level caching cannot be trusted across platforms.

    It can be viewed and manually cleared at chrome://net-internals/#dns. source

  4. Preconnect domain cache

    A unique and important optimization in Chrome is the ability to remember the set of domains used by all subresources referenced by a page. Upon the next visit to the page, Chrome can preemptively perform DNS resolution and even establish a TCP connection to these domains.

    This cache can be viewed at about:dns. source

  5. V8 compilation cache

    Compilation can be an expensive step in executing JavaScript. V8 stores compiled JS keyed off of a hash of the source for up to 5 generations (garbage collections). This means that two identical pieces of source code will share a cache entry regardless of how they were included. source

  6. SSL session cache

    Caches SSL sessions to disk. This saves several round trips of negotiation when connecting to HTTPS pages by allowing the connection to skip directly to the encrypted stream. Implementation and limits vary by platform, as an example, when OpenSSL is used, the limit is 1,024 sessions. source

  7. TCP connections

    Establishing a TCP connection takes about one round trip time. Newer connections also have a smaller window so they have a lower effective bandwidth. For this reason Chrome keeps connections open for a period in hopes that they can be reused. This can be thought of as an in-memory cache.

    Connections may be viewed at chrome://net-internals/#sockets and cleared programmatically by calling chrome.benchmarking.closeConnections() when Chrome is run with the --enable-benchmarking flag set. source

  8. Cookies

    While not usually thought of as a cache, this is web page state which is persisted to disk. The presence of cookies can have a large impact on performance. They can bloat requests and change how the client and server behave in limitless ways.

    They can be cleared manually at chrome://settings/advanced. We are planning to add a method to chrome.benchmarking for the same. source

  9. HTML5 caches

    HTML5 introduces 3 major new ways for web pages to persist state to disk: Application Cache, Indexed Database and Web Storage. For a particular page, these stores may be viewed under the "Resources" panel of the Inspector. The entire Application Cache may also be viewed and manually cleared at chrome://appcache-internals/

  10. SDCH dictionary cache

    While currently only used by Google Search, the SDCH protocol requires a shared dictionary to be downloaded periodically. A performance hit is taken infrequently to download the dictionary which makes future requests much faster. source

I hope you found this as interested as I did. Please let me know if I left anything out.

Edit: Will Chan points out additional caches for proxies, authentication, glyphs and backing stores. Proxy and authentication caches were intentionally omitted because they aren't relevant to our benchmark, however glyphs and backing stores are two additional things we need to consider. Thanks!

Sunday, January 30, 2011

Edit recorded web pages

Web Page Replay now supports editing recorded web pages. Credit for the idea goes to Sergey Chernyshev. To use it: record a web page then use httparchive's edit command.

$ ./httparchive.py edit --host=example.com --path=/ ~/archive.wpr

Since all other aspects of the page are controlled, this allows you to measure the effect of tweaking just a single aspect of the page without having to worry about conflating factors like rotating ads, server response time or variable network conditions.

I'm finding this invaluable for answering quick questions like how much faster a page loads if this script were deferred or if the order of this script and stylesheet were switched.

Friday, January 28, 2011

The WebKit PreloadScanner

WebKit's HTMLDocumentParser frequently must block parsing while waiting for a script or stylesheet to download. During this time, the HTMLPreloadScanner looks ahead in the source for subresource downloads which can be started speculatively. I've always assumed that discovering subresources sooner is a key factor in loading web pages efficiently, but until now, never had a good way to quantify it.

How effective is it?

Today I used Web Page Replay to test a build of Chromium with preload scanning disabled vs a stock build. The results were definitive. A sample of 43 URLs from Alexa's top 75 websites loaded on average in 1,086ms without the scanner and 879ms with it. That is a ~20% savings!

That number conceals some subtleties. The preload scanner has zero effect on highly optimized sites such as google.com and bing.com. In stark contrast, the preload scanner causes cnn.com, a subresource heavy site, to load fully twice as fast.

Why does this matter?

There is a lot of room for improvement in the preload scanner. These results tell me that it is worth spending time giving it some serious love. Some ideas:

  • It doesn't detect iframes, @import stylesheet, fonts, HTML5 audio/video, and probably lots of other types of subresources.
  • When blocked in the <head>, it doesn't scan the <body>.
  • It doesn't work on xhtml pages (wikipedia is perhaps the most prominent example).
  • The tokens it generates are not reused by the parser, so in many cases parsing is done twice.
  • The scanner runs in the UI thread. So as data arrives from the network, it may not be scanned immediately if the UI thread is blocked by JavaScript execution.
  • External stylesheets are not scanned until they are entirely downloaded. They could be scanned as data arrives like is done for the root document.

Test setup

The test was performed with a simulated connection of 5Mbps download, 2Mbps upload, and a 40ms RTT time on OSX 10.6. The full data set is available.

Thursday, May 8, 2008

Milliseconds Matter

In the pursuit of creating blazingly fast web applications, we spend hours benchmarking, profiling, and optimizing to shave off precious milliseconds. But what is a millisecond?

I found it interesting to take a step back today and remind myself what a millisecond feels like.

Enter a delay in the text box and then click the white boxes to reveal a super secret message. The delay represents hard work the app has to perform in order to decrypt the message.

At what threshold does the delay begin to annoy you? For me it was about 300ms.



Inline frame not working? Here's a link to the demo.

Monday, May 5, 2008

JsBench Help

JsBench now has a help page with information on how to get started, an explanation of the benchmark methodology, and some examples. Check it out and feel free to let me know if anything is missing.

Saturday, April 26, 2008

JsBench

In the search for techniques to produce truly blazing JavaScript, I've found lots of conflicting and downright misinformation on the web. When Googe's App Engine was released, I decided to kill two birds with one stone: (1) play with App Engine and (2) create a tool for sharing real world cross browser JavaScript benchmarks.

Try it out:
http://jsbench.appspot.com/

JsBench

If you aren't sure how to get started, check out some of my experiments.

For instance, did you know that using the Boolean() constructor is perhaps the slowest way to create a boolean? In some cases it can be as much as an order of magnitude slower than alternate methods.
http://jsbench.appspot.com/#suite=agdqc2JlbmNocgwLEgVTdWl0ZRiaAgw

Also, if you've been using a for..in loops when they aren't necessary, your code is probably running more than ten times slower than it has to if you'd used a typical for-loop and cached the length.
http://jsbench.appspot.com/#suite=agdqc2JlbmNocgsLEgVTdWl0ZRgNDA


Oh, and the last bit for now is that you better not even think of omitting the "var" statement when creating a new variable. Doing so could be slowing you down by two orders of magnitude!
http://jsbench.appspot.com/#suite=agdqc2JlbmNocgwLEgVTdWl0ZRjqAgw

Look for some more specific, in depth studies to come. What examples can you come up with?

FireBUGs

After a long period of forgetting this blog existing, I figured its about time to pick it up again. Recently I've become interested in lending a hand to the Firebug Working Group to help fix some of the 341 open bugs and feature requests:
http://code.google.com/p/fbug/issues/list

Here's my first patch:
http://code.google.com/p/fbug/issues/detail?id=43

Sunday, December 17, 2006

Fasterfox 2.0.1

I'm experimenting with a new speed test feature in Fasterfox. Here's a sneak preview of Fasterfox 2.0.1 which includes this new feature:
Fasterfox 2.0.1

To use: right click the Fasterfox icon and select "Speed Test." The speed test allows you to measure the mean/median load time for a specified number of loads of a given web page. This is very useful for those wanting to test the rendering speed of their webpage.

Saturday, December 16, 2006

CSS Shorthand Colors

I have seen plenty of articles on CSS shorthand (tips for conserving bytes in your CSS syntax), but I have never seen one that mentions when to use color names over their hexidecimal codes.

There are 16 colors names which are supported by all major browsers:
  1. white: #fff
  2. yellow: #ff0
  3. red: #ff0
  4. fuscia: #f0f
  5. silver: #c0c0c0
  6. gray: #808080
  7. olive: #808000
  8. purple: #800080
  9. maroon: #800000
  10. aqua: #f00
  11. lime: #0f0
  12. teal: #008080
  13. green: #008000
  14. blue: #00f
  15. navy: #000080
  16. black: #000

While there is a longer list of color names which are supported by some browsers, the 16 above are the only ones which can safely be assumed to be properly supported by all browsers. Its trivial to see that for 9 of the 16 colors it is shorter to use the color name, while in the other 7, its better to use the hex value.

Here are the color names you should remember and use over their hex values:
  1. red
  2. silver
  3. gray
  4. olive
  5. purple
  6. maroon
  7. teal
  8. green
  9. navy