Wednesday, October 8, 2008
One of our early goals for Google Chrome was to make the browser as fast as we possibly could. But in addition to raw speed, we wanted it to be highly responsive. After all, it doesn't matter how fast pages can be loaded if the user interface is locked up!
To understand our holistic approach to performance in Google Chrome, it helps to know some background. Processing speed per dollar has rapidly increased over the last 40 years, but hard drives, which are based on moving parts, do not improve nearly as fast. As a result, a modern processor can execute millions of instructions in the same time that it takes to read just one byte off disk. We knew that building a fast, responsive browser for modern systems would require extra attention to disk I/O usage.
Developers are ideal testers for I/O performance since the load of compiling a very large application like Google Chrome will bog down even the most powerful system. This soon led us to a rule that the main thread of Google Chrome—the thread that runs the entire user interface—is not allowed to do any I/O. After fixing the obvious cases, we ran a program that thrashes the disk while we profiled common operations in Google Chrome to find latent I/O hotspots. We even ran a test where we removed the privileges of the main thread to read or write to disk, and made sure that nothing stopped working.
We moved the I/O onto a number of background threads which allow the user-interface to proceed asynchronously. We did this for large data sources like cookies, bookmarks, and the cache, and also for a myriad of smaller things. Writing a downloaded file to disk, or getting the icons for files in the download manager? The disk operations are being called from a special background thread. Indexing the contents of pages in history or saving a login password? All from background threads. Even the "Save As" dialog box is run from another thread because it can momentarily hang the application while it populates.
Startup poses a different type of problem. If all the subsystems simultaneously requested their data on startup, even if it was from different threads, the requests would quickly overwhelm the disk. As a result, we delay loading as much data as possible for as long as possible, so the most important work can get done first.
Our startup sequence works like this: First chrome.exe and chrome.dll are loaded. Then the preferences file is loaded (it may affect how things proceed). Then we immediately display the main window. The user now can interact with the UI and feels like Google Chrome has loaded, even though there has been remarkably little work done. Immediately after showing the window, we create the sub-process for rendering the first tab. Only once this process has loaded do subsystems like bookmarks proceed, since any I/O contention would slow down the display of that first tab (this is why you may see things like your bookmarks appear after a slight delay). The cache, cookies, and the Windows networking libraries are not loaded until even later when the first network request is issued.
We carefully monitor startup performance using an automated test that runs for almost every change to the code. This test was created very early in the project, when Google Chrome did almost nothing, and we have always followed a very simple rule: this test can never get any slower. Because it's much easier to address performance problems as they are created than fixing them later, we are quick to fix or revert any regressions. As a result, our very large application starts as fast today as the very lightweight application we started out with.
Our work on I/O performance sometimes complicates the code because so many operations have to be designed to be asynchronous: managing requests on different threads with data that comes in at different times is a lot of extra work. But we think it has been well worth it to help us achieve our goal of making Google Chrome not only the fastest, but the most responsive browser we could build.