With the work going on to bring Firefox to mobile devices, and with desktop users demanding more and more from their web browser, memory usage is a concern. Even with 4GB on desktop and laptops becoming commonplace, and 8GB, 12GB, 16GB etc. becoming not all that unusual, it’s unnerving to see a web browser eating up a large chunk of that. I’ve been spending time figuring out how we can improve our memory usage, which starts with finding out where Firefox uses memory to begin with.
Let’s get one thing out of the way up front. Today’s web browser is in many ways acting like a miniature full operating system. It runs multiple applications at once (whether in multiple windows or tabs). It might do a lot of background processing. It can work with large data sets, for example large images on flickr or large spreadsheets on Google Documents. But, the final memory usage number that the user sees when they open up the Task Manager or Process Viewer is the aggregate memory usage of the entire system. So, the goal of improving our memory usage is not to get that number to the lowest possible — doing that would be an unacceptable tradeoff in performance for users — but instead to understand where memory is being used, and then use that data to improve in those areas as possible.
One comment that I’ve heard is that Firefox 3.6 seems to use more memory than Firefox 3.0. My initial tests show this to not be true; specifically, I looked at the “Private Bytes” value in the Windows 7 task manager shortly after startup with about:blank, and also after opening a number of tabs (gmail, google docs, cnn.com, front page of the boston.com big picture blog, engadget, and a few others). Here are the results of a typical run:
| (in kb) | Firefox 3.0 | Firefox 3.6 |
|---|---|---|
| Blank Page | 20,052 | 21,740 |
| Multiple Tabs | 115,532 | 109,128 |
The next question is figuring out where all the memory goes. I’ve been adding some instrumentation to Firefox to figure out in more detail where memory is being used. For a sample run with the multiple tabs shown above, here’s what some of that reporter data looks like:
| Component | Memory (in kb) |
|---|---|
| Windows – Private Bytes | 111,616 |
| jemalloc – Commit Size | 91,684 |
| JavaScript – GC Chunks | 11,534 |
| JavaScript – NJ Trace Code | 128 |
| JavaScript – js_malloc Other | 30,142 |
| Images (uncompressed) | 53,811 |
| Graphics Surfaces (win32) | 53,967 |
| PresShell Arenas | 6,373 |
Or, graphically:

There’s some overlap in those numbers — for example, the jemalloc commit size is a subset of the Windows Private Bytes number, and most of the rest is a subset of the jemalloc commit size. Likewise, the uncompressed images number is a subset of the Win32 graphics surfaces number; that is, ~53MB is in use by win32 surfaces, and almost all of that is due to live images in pages (remember that we’ve got some image heavy sites in that tab set, including the Big Picture blog which has around 10-11 large images on it… those should account for about 20-25MB just by themselves).
There are some things that don’t make sense in the above, which mean that my instrumentation isn’t quite correct… for example, adding up the JS numbers, the Images number, and the PresShell arenas number brings us beyond the jemalloc commit size, which shouldn’t be true. However, some of the Image data is allocated by GDI, likely bypassing jemalloc, so we have to take that into account. There’s also some large other chunks of the browser that have yet to be instrumented, which should provide additional insight.
Two initial observations: one, keeping images compressed in memory and only decompressing them briefly when we need to draw them is a potential huge memory win. We have the infrastructure and code to do this in place; it was disabled recently while some of the internals changed, and it needs to be reenabled.
Two, the 30MB or so in the “js_malloc Other” bucket is also pretty curious. We need to do some more work to figure out what exactly is in here. (This contains things like data structures for tracking array contents and — potentally a big one — string data.)
I’ll be blogging more as the instrumentation takes shape, and as it gets landed into trunk nightly builds. Much of this information will be visible in about:memory, and eventually we’ll be able to give some per-tab memory information as well.