The Gecko Profiler now supports profiling JS along side with C++ thanks to the amazing work of Alex Crichton. Note that this feature is available across all platforms including desktop, Android and B2G! We’ve already been testing it out over the past few weeks and it has already yield important insight into performance bottlenecks caused by js scripts (766333, 767070, 774811, 777266, 765930, and more) as well as it has identified many problematic add-ons we plan to follow up on.
This new tool will let us make sure the browser is always snappy. Unlike other profilers each sample is not treated equality but instead looks for areas of the code that block the responsiveness of the browser. As Taras calls it, this is an interactivity profiler. Below are some of the examples of problems with profiles and an explanation of the diagnostic we derived.
Example 1: document.write() and Adblock
Here’s an example of the data we can collect, you can see the profile yourself here. In this profile the forum has a script called writeLink() which invokes Document.write() repeatedly. This is something that should be avoided as we can see further down the stack it causes synchronous HTML5 parsing to happen for each call to document.write(). This in of itself is slow but not enough for a web author to notice on its own. However with adblock installed this causes the content policy to be checked for the whole document after each and every call to Document.write() slowing this page to a crawl.
Forum+Adblock jank caused by document.write interaction
Example 2: Thumbnail blocking the main thread
Here’s an example of a JS call that takes over 125ms to return leading to a short UI stutter. Follow along with the profile. Here we have a timer invoking ‘Thumbnails_capture() @ browser.js:5552′ (outside the screenshot, see the profile link). This causes the page to be rendered into an offscreen surface then compressed as a PNG. This particular page happens to have a WebGL context which makes the matters worse. To get a WebGL context into an offscreen image, as opposed to the screen in a hardware accelerated configuration, required this surface to be read back causing a synchronous GPU flush. Then the page must be painted and composite into the offscreen image. Once this is completed the image is encoded to a PNG. Running all these steps at once is enough to block the UI for 0.1 seconds. While this isn’t particularly long it is enough for a video, scrolling and animations to briefly feel jerky.
The solution in this case is to break up the image rendering and the encode into two separate events. Yielding two shorter pauses of 60ms letting the animations run smoother. In the future we will want to either optimize, break down or move off the main thread the rendering and the PNG encoding.
Thumbnail code causing Jank
Example 3: Flushing layout from JS
Laying out the page is a difficult process that is heavily optimized in all major browsers. For the most part this step is invisible to most pages and Just Works. The real problem occurs when a script queries the position of an element after it has modified the page. When the page is modified the layout state is marked dirty and any query to it will cause your page to be laid out synchronously to calculate the up-to-date position of your element.
Here’s a real world profile of this we found in the nightly version of Fennec (this code wasn’t shipped). In this case we where doing something that resembles this:
var y1 = myElement1.offsetLeft; // <-- Causes a layout flush
myElement2.style.left = y1 + 5; // <-- Dirty layout information
var y2 = myElement2.offsetLeft; // <-- Causes another layout flush
myElement2.style.left = y2 + 5; //
<-- Dirty layout information
// Repeat a few times
For a page of the complexity of CNN.com a layout will take more then 50ms+ on mobile, now doing 4 layouts synchronously from JS just to layout a few elements near each other instead of using HTML/CSS will cost you over 200ms. You will have likely dirty the layout information before your script is finished needing a layout and finally a paint so you’ve just blocked the main thread for over 0.4 seconds on mobile. Be very careful to avoid triggering synchronous layout!
JS Flushing Layout synchronously to get position information
Find your own examples and blog them!
With the new JS support is now easier to diagnose issues so try it out, file bug and write about your commonly found anti-patterns!