Many teams are busy making sure Firefox 4 is a great experience on both desktop and mobile as we get ready for the release candidate. To highlight just one, the Sync team has recently put in extra effort to make sure setting up Sync on a mobile device is a smooth process. In particular, for users with a lot of history, syncing all that data for the first time could result in sluggish browsing as Firefox creates millions of temporary JavaScript objects in memory. We’re glad to report that not only Sync in Firefox 4 will use much less memory but also the Prospector team has leveraged the tools and learning to reduce memory usage in Home Dash!
The Home Dash prototype is a Prospector experiment that has rapidly changing code, but that doesn’t mean it should always trade off efficient memory usage for easy-to-change code. By using the memory tracking tool from the Sync and JavaScript teams, we can easily spot the code that results in the most inefficient uses and streamline them one by one while keeping the rest of the code flexible.
Before you read on to the details of various optimizations, you can install the snappier Home Dash on a Firefox 4 beta. You can also check out the full list of changes since Home Dash 6, leave feedback, and contribute!
Most of the following memory tweaks aren’t surprising or too creative, but that also means as the platform becomes smarter and more efficient, these manual optimizations might not be necessary in the future!
Short circuit to avoid functionally identical work
Sometimes a source string is the same as resulting string after calling replace (when the pattern doesn’t match) or slice (when the string is already short enough), so if a new string object isn’t needed, a simple test can be inserted to just return the original string. Similarly, short circuiting before code that functionally does nothing, e.g., zeroLengthArray .slice() .forEach(closure), can avoid creating temporary arrays and functions.
Lazily create objects when possible
One feature of Javascript 1.7 is destructuring assignment, which also works in function parameters. However doing something like function({title, url}) requires all callers to pass in an object, and this might mean a temporary object needs to be created if only the individual parts (title and url) are available. As with traditional lazy concepts, only when the actual object or data is needed should it then be created from the parts and saved for later.
Move constant objects and functions out of loops and functions
For frequently called functions, avoid code like [1,2,3].forEach(function(num) ..) and instead 1) stash the constant array outside of the function — perhaps as a property of the function and 2) convert the .forEach(closure) to a plain for loop. This avoids creating 2 objects every time the function is called by sharing the array across calls and not creating a new closure each time. Alternatively, if the closure doesn’t actually use values from its containing scope, the forEach callback could be defined outside of the function.
Avoid closures for extremely hot functions
It seems like the SpiderMonkey engine will automatically create a Call object every time a function is called if that particular function has a closure that potentially accesses a local variable of the containing function. If the code can be restructured to not have the closures, it’ll prevent one extra object being created on each call, which can be significant for a function that is called millions of times and doesn’t otherwise create any objects.
But even with these optimizations to reduce how many objects are created when a function is called, the best way to reduce the number of objects being created is to call the function fewer times or not at all when possible. While garbage collectors and memory allocators become more efficient and pause the interface less, giving them significantly less work can save a lot of headache for everyone. In fact, for the history search in Home Dash, instead of creating literally millions of objects every second, it now only creates a few thousand by only calling the matching function when necessary, so searching should feel much snappier!