Neil's Place

July 13, 2015

Comparing Flexible Box Layouts

Filed under: Uncategorized — enndeakin @ 8:53 am

I decided to take a look at what would happen if I replaced some code in Firefox such that instead of XUL box layout, CSS flex layout was used. The latter is generally a standardized version of XUL box layout using only CSS properties. It supports the same properties and values using different names, and has a few additional features. One possible way to implement this is to just set ‘display: flex’ on elements by default, and assign some rules to map the various attributes to the CSS flex properties. This isn’t enough though and just makes the UI look all confused. I then tried extending the CSS flex implementation to check for the XUL attributes as well as the existing XUL flex properties, but only for XUL elements. This worked slightly better.

It turns out that CSS flex doesn’t care for things that aren’t blocks or flexible elements as children, so I needed to tweak some checks to allow for certain types of XUL layout types that have no standard equivalent (such as the deck element needed for displaying a tabbed browser). I also disabled an assertion, but since this is just an experiment, I didn’t feel that was important. Finally, I was able to get a working Firefox browser.

It looks pretty good. There are a few glitches here and there that could be fixed up, for example, the titlebar overlaps the tab bar, and the back button and location field box overlap a bit, but it mostly looks and works fairly well. The preferences panel and other dialogs also work reasonably well. I had to disable menus, tooltips and panels though and didn’t investigate why they didn’t work.

Let’s take a look at how well this special version of Firefox performs.

XUL flexbox spends 16 milliseconds of time in reflow during Firefox startup. CSS flex spends 217 milliseconds.

Not very well actually. The CSS flex version spends over 13 times more time within the layout/reflow step than the XUL box version. When I then tried this in a debug build, there was noticeable delay while interacting with the browser when significant layout steps were needed, such as opening the sidebar.

There are several possible explanations. One is that CSS flex is just slower in general. Another is that it has trouble mixing flexible boxes with specific XUL layout types that are used within the Firefox UI (stacks, decks, etc) that I didn’t change. Or perhaps there are some specific arrangements of elements that cause an issue. In a debug build, some layout and flexbox related warnings are printed out; these could also account for performance issues. For example, some code may be getting confused, printing a warning, then trying to lay out some elements again; I’m not familiar enough with the implementation to know for sure. Let’s dig deeper.

I first tried a number of simpler XUL testcases, and found that performance was still better with XUL boxes, although not by an order of magnitude. I decided then to switch to an entriely HTML testcase, where any other aspects of XUL wouldn’t hopefully be involved. First, I tried a simple HTML testcase using the CSS flex type compared to the -moz-box (XUL box) type on the same element. I used a fresh optimized build of Mozilla code with only minor changes to disable scrollbars for this and the following tests. I loaded the test and measured the time spent.

The first test involves a single parent element with either of the two layout types with a variable number of children, up to 5000, as shown in the chart.

XUL box scales relatively well, with With 5000 children, in a horizontally laid out box, around 300 milliseconds is spent during reflow, with XUL box being about 8 percent higher. For a vertically laid out box, this is around 150 milliseconds, again with XUL box being about 8 percent slower. At a smaller number of children, the difference in all types is negligible.

The first thing to notice is that a horizontally laid out element is slower than a vertical one for both XUL and CSS flex layout. I’m sure some layout developer could explain why that might be the case. Second, the XUL layout scales slightly worse than the CSS layout, although there isn’t a noticable difference at small values.

The second test involves a structure of a few elements mixed with some text. To check the scalability, I copied this structure of elements multiple times and nested them inside each other to create a very deeply nested structure many elements deep. I tried this test first with a mixture of horizontal and vertical layout, then with all elements set to use horizontal layout.

For this test, CSS flex is slightly worse at scaling to very deeply nested structures than XUL box layout, although again, only by a small amount. I then tried the same test where every element was assigned a horizontal layout. The XUL flex case is slightly worse, although not significantly. The CSS flex case however becomes significantly worse quite quickly, taking almost twice as long.

After I made this chart, I tried a case with only vertical laid out elements. Unfortunately, the XUL test case with only vertical elements got into some kind of infinite loop situation so I wasn’t able to make a direct comparison. However, the CSS flex case did appear to perform much better in this case, and scaled much better.

The obvious conclusion is that CSS flex layout is slow at horizontal layout. But that still might not be the case. One theory I have is that the overflow and scrolling mechanism for the document causes a delay only in the horizontal direction. I may investigate that further later. But even if CSS flex is slower horizontally, that doesn’t fully explain the extra 200 milliseconds of time taken to start Firefox. The tests done here tests scalability, but noticeable delays don’t really start until much higher values of children or nested structures, generally higher complexity than I would expect the Firefox UI to actually have.

For fun, I decided to add a rule that made almost every element a XUL box. The rule below should handle most cases, except for other style rules where the important marker is also used and a number of specific XUL elements that are mapped by tag name.

* { display: -moz-box !important; }

The result is that 40 milliseconds was taken up doing reflow at startup to load the initial page. A bit slower than the 16 milliseconds from before but still faster than CSS flex. I also tried replacing the rule above with one that used CSS flex by setting the display property to ‘flex’ but still maintaining the important marking. The result was that 49500 milliseconds of reflow time was spent during start up. You read that right: 49 thousand. That’s almost 50 seconds. I’ve no idea what could be causing such a drastic slow down when CSS flex is used, but that clearly isn’t good.

With the investigation so far, I can only conclude that it looks as if CSS flex box is not something that can be dropped into the Firefox UI with minimal changes, as I thought it might be. While it offers some feature advantages and fixes issues that XUL box layout has with inline text, the startup performance degradation with a simple fix is quite high. More detailed work and investigation is needed to determine the causes of the performance problems to determine whether there are specific element structures that cause problems that could be avoided, whether certain XUL layout types mixed with flexible boxes cause errors that can be fixed, or whether significant additional performance optimizations need to be done.

Advertisements

6 Comments »

  1. Where do the 16 and 217 numbers for initial time spent in reflow come from, ie how did you measure? Does “reflow” include initial layout? I’m surprised, because whenever I’ve looked at startup profiles, timing for adding single buttons was already in the several-ms range, so it surprises me a lot that your number for XUL is so very low.

    Finally I’d point out that display: -moz-box doesn’t actually cause flexing until you also add -moz-box-flex. I don’t think that’s true in the case of css flexbox, though I could be wrong.

    I’d also be interested to see the patches you used for these tests, and to know if you’ve tried talking to dholbert, who is the resident CSS flexbox expert.

    Comment by Gijs — July 13, 2015 @ 11:09 am

  2. Is the “Time spent in reflow during Firefox startup” on debug or optimized build? Debug builds in general execute a lot more code, and obviously not optimized at all. It is not just printing couple of warnings. In DOM for example some
    O(1) algorithms become O(n) in debug builds, since we verify+assert certain states. So, I wouldn’t take any numbers from a debug build.

    Comment by smaug — July 13, 2015 @ 11:21 am

    • All of the numbers are from optimized builds

      Comment by enndeakin — July 13, 2015 @ 8:10 pm

  3. Yeah, there are definitely some pathological cases with nested CSS flexbox (at least for our implementation), where we have to reflow twice at each nesting level, and that blows up to take n^2 work.

    This is usually caused by one or more of the following:
    (1) “align-items:stretch” (which means we have to snap each flex item to the same height, in a horizontal flexbox; this requires us to give a 2nd reflow to any items whose heights need to grow, after we’ve established all of the items’ initial heights).

    (2) “flex-basis:auto” (which requires us to measure the main-size of a flex item before we can do any flexing. This requires two-pass reflow of the flex item, if it’s in a vertical flexbox).

    (3) “min-width:auto”/”min-height:auto” (same as (2))

    (4) percent-height children inside of a flex item. (which prevent us from optimizing away 2-pass reflows that result from (1), (2), or (3), since we have to reflow the children with the final specified height)

    Unfortunately, (1) (2) and (3) are all the default values of these properties, so they’re easy pathological cases to fall into. :-/ Fortunately it’s not too bad if you don’t nest very deeply, and we also have optimizations to avoid clearly-useless relayouts (e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=1054010 ) but there’s likely a lot more room for optimization here. (And (4) kills the main optimizations.)

    In the meantime, I’d be interested to know if adding this rule…
    * { align-items: flex-start }
    …improves the performance of your horizontal-flexbox cases.

    Comment by Daniel Holbert — July 13, 2015 @ 11:22 am

  4. (I’d be very interested in profiling a build with your patches, to see where we’re falling over & see what we should prioritize to make CSS Flexbox in Firefox UI a more attractive proposition.)

    Comment by Daniel Holbert — July 13, 2015 @ 11:39 am

  5. Great work. A regression of 200ms actually doesn’t sound that bad – it looks a very good base to start a refactoring.

    I assume that you profiled startup to get your numbers. Did you notice anything more precise?

    Comment by yoric — July 13, 2015 @ 12:26 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: