Firefox 4: Better performance with Lazy Frame Construction

Lazy Frame Construction is new to Gecko and allows many DOM operations (appendChild, insertBefore, etc) to not trigger immediate reflows. This can vastly improve the interactive performance of very complex web pages. If you want to test this out, you should get a Firefox Nightly.

Lazy frame construction recently landed on mozilla-central. To explain what this means and how this improves things we need some background. Each node in the DOM tree of a webpage has a frame created for it that is used to determine where on the page the node is drawn and its size. A frame corresponds closely to the concept of a box from the CSS spec. We used to create frames for DOM nodes eagerly; that is as soon as a node was inserted into the document we would create a frame for it. But this can create wasted effort in many situations. For example if a script inserts a large number of nodes into the DOM we would create a frame for each node when it is inserted. But with lazy frame construction we can process all those nodes at once in a big batch, saving overhead. Furthermore the time it takes to create those frames no longer blocks that script, so the script can go and do what it needs to and the frames will get created when they are needed. There are other situations where a script would insert nodes into the document and remove them immediately, so there is no need to ever create a frame for these as they would never be painted on screen.

So now when a node is inserted into a document the node is flagged for needing a frame created for it, and then the next time the refresh driver notifies (currently at 20 ms intervals) the frame is created. The refresh driver is also what drives reflow of webpages and CSS & SVG animations.

Let’s look at two examples where lazy frame construction helps.

In this example we insert 80000 div elements and then we flush all pending layout to time how long it takes before the changes made by the script are done and visible to the user. The script can continue executing without flushing layout, but we do it here to measure how long the actual work takes.

With lazy frame construction we are able to process the insertion of all 80000 div elements in one operation, saving the overhead of 80000 different inserts. In a build without lazy frame construction I get an average time of 1358 ms, with lazy frame construction I get 777 ms.

This example comes from a real webpage. We append a div and then set “div.style.position = ‘absolute’;”, and repeat that 2000 times, and then we flush all pending layout to time how long it takes before the changes made by the script are done and visible to the user.

With lazy frame construction we don't even bother creating the frame for the div until after the position has been set to absolute, so we don't waste any effort. In a build without lazy frame construction I get an average time of 4730 ms, with lazy frame construction I get 130 ms.

40 comments

The second test gives an impressive result indeed, but the first test scores 8000ms both on Firefox 3.6.4 (test build) and 8000 on Minefield as well. No idea if my situation is special or there is something wrong…

That’s pretty cool, it’s a step closer to something that I really wanted to see in dom for ages now. And that’s sort of atomic operations, as in: document.atomic(function(){/* tons of dom manipulations here */}) where atomic would not do any redraws, or something of that sort. Of caurse you can achieve some of that now with innerHTML, but not all. Anyway thats’s off topic.

Lazy frame construction is a good idea and it’ll make a lot of pages faster. Good job guys.

Pavel, all JS scripts are “atomic” in the sense you describe. The only question is what the UA manages to defer to the end of the script execution. Pretty much any modern UA defers layout, style computation, painting. Some defer rendering object construction (that’s what this article is about).

If you do el.offsetHeight, then the browser has to do layout (but not drawing). This would be the case no matter what, even if the “atomic” API you suggest were added (since anything else would lead to weird races and scripts working or not depending on hardware, os, time of day, etc details).

Firefox 3.5.9 [Fedora] release.
Test 1 took around 4k in first second and third attempt.
Test 2 took around 9k in first attempt,60K in second and had to kill Firefox in third attempt as the CPU usage was 100% for more than 3 minutes i tested it for.

Tested the same script with Chrome 5 [Fedora] and the biggest difference that i saw was the freezing irrespective of the time taken by each. Running the script in Firefox freezes the whole application,Switching to another tab in FF was not possible. Doing the same in Chrome just freezes that tab, while one can use other tabs independent of the one under test. Testing in Chrome seems like different browser, though it was tested on same lines as FF.
Really sad to see the freeze on Firefox.

I don’t think these micro-benchmark are fair for cross-browser comparison.

Anyway, it would be nice to have another more real-world benchmark using document.createDocumentFragment(). Indeed I thought that this method was created to tackle this issue in the first place.

And I agree with Pavel Dudrenov : having a new document.atomic function that allow developper to control browser reflow could be interesting. Having “atomic” reflow could benefits to a lot of web app, but it could also lead to a lot of trouble if it is misused….

first test 981
second test 407
not sure if this is intented but if i try the test again without refreshing the page
those number double (or so)each time so it can get humongous very quick!
ty for the research love coder who try to simplify instead of complicating
(a la html5 for exemple)instead of learning the full power of the tool they have at hand like ecmascript5.keep up the good work!

Yes. The exact timing might be up to change; there’s a good argument that the right number is 60Hz, not 50Hz.

Note that your typical modern LCD monitor only refreshes at 60Hz, so painting faster than that is completely pointless.

> And Flash Player cannot render faster then that?

It can’t update the on-screen display faster than 60Hz, no. It can, of course, push bits around at a higher rate than that, but no one will see some of those bits. There’s no problem pushing the bits around in Gecko either; it’s just not useful.

Sorry for some off-topic. What about other browsers?
Opera 10.54: first test: 1335ms, second test: 143ms
Opera 10.10: first test: 1255ms, second test: 155ms
Opera 9.64: first test: 2608ms, second test: 163ms
Opera 8.54: first test: 3950ms, second test: 100ms

[…] URL field without reloading the page using HTML History APIsMore responsive page rendering using lazy frame constructionLink history lookup is done asynchronously to provide better responsiveness during pageloadCSS […]