Great articles, like Dave Hyatt’s Writing Efficient CSS, helped developers adapt to a rudimentary selector matching landscape. We learned from Steve Souders (and others) that selectors match from right to left, and that certain selectors were particularly arduous to match and should best be avoided. For example, we were told that descendant selectors were slow, especially when the right-most selector matched many elements on the page. All this was fantastic information when we had none, but as it turns out, times have changed. Thanks to some amazing work by Antti Koivisto there are many selectors we don’t need to worry about anymore.

Antti contributes code to WebKit core and recently spent some time optimizing CSS selector matching. In fact, after finishing his work, he said:

“My view is that authors should not need to worry about optimizing selectors (and from what I see, they generally don’t), that should be the job of the engine.”

~ Antti Koivisto

Wow! That sounds fantastic to me. I’d love to be able to use selectors in a way that makes sense for my architecture and let the rendering engine handle selector optimization. So, what did he do? Not just one thing, rather he created multiple levels of optimization — we’ll take a look at four optimizations in particular:

  1. Style Sharing
  2. Rule Hashes
  3. Ancestor Filters
  4. Fast Path

Style Sharing

Style sharing allows the browser to figure out that one element in the style tree has the same styles as something it has already figured out. Why do the same calculation twice!

For example:

<div>
  <p>foo</p>
  <p>bar</p>
</div>

If the browser engine has already calculated the styles for the first paragraph, it doesn’t need to do so again for the second paragraph. A simple but clever change that saves the browser a lot of work.

Rule Hashes

By now, we all know that the browser matches styles from right to left, so the rightmost selector is really important. Rule hashes break a stylesheet into groups based on the rightmost selector. For example the following stylesheet would be broken into three groups:

a {}
div p {}
div p.legal {}
#sidebar a {}
#sidebar p {}
a p p.legal
a {} div p {} div p.legal {}
#sidebar a {} #sidebar p {}  

When the browser uses rule hashes it doesn’t have to look through every single selector in the entire stylesheet, but a much smaller group of selectors that actually have a chance of matching. Another simple but very clever change that eliminates unnecessary work for every single HTML element on the page!

Ancestor Filters

The ancestor filters are a bit more complex. They are Probability filters which calculate the likelihood that a selector will match. For that reason, the ancestor filter can quickly eliminate rules when the element in question doesn’t have required matching ancestors. In this case, it tests for descendant and child selectors and matches based on class, id, and tag. Descendant selectors in particular were previously considered to be quite slow because the rendering engine needed to loop through each ancestor node to test for a match. The bloom filter to the rescue.

A bloom filter is a data structure which lets you test if a particular selector is a member of a set. Sounds a lot like selector matching, right? The bloom filter tests whether a CSS rule is a member of the set of rules which match the element you are currently testing. The cool thing about the bloom filter is that false positives are possible, but false negatives are not. That means that if the bloom filter says a selector doesn’t match the current element, the browser can stop looking and move on the the next selector. A huge time saver! On the other hand, if the bloom filter says the current selector matches, the browser can continue with normal matching methods to be 100% certain it is a match. Larger stylesheets will have more false positives, so keeping your stylesheets reasonably lean is a good idea.

The ancestor filter makes matching descendant and child selectors very fast. It can also be used to scope otherwise slow selectors to a minimal subtree so the browser only rarely needs to handle less efficient selectors.

Fast Path

Fast path re-implements more general matching logic using a non-recursive, fully inlined loop. It is used to match selectors that have any combination of:

  1. Descendant, child, and sub-selector combinators, and
  2. tag, id, class, and attribute component selectors

Fast Path improved performance across such a large subset of combinators and selectors. In fact, they saw a 25% improvement overall with a 2X improvement for descendant and child selectors. As a plus, this has been implemented for querySelectorAll in addition to style matching.

If so many things have improved, what’s still slow?

What is still slow?

According to Antti, direct and indirect adjacent combinators can still be slow, however, ancestor filters and rule hashes can lower the impact as those selectors will only rarely be matched. He also says that there is still a lot of room for webkit to optimize pseudo classes and elements, but regardless they are much faster than trying to do the same thing with JavaScript and DOM manipulations. In fact, though there is still room for improvement, he says:

“Used in moderation pretty much everything will perform just fine from the style matching perspective.”

~ Antti

I like the sound of that. The take-away is that if we can keep stylesheet size sane, and be reasonable with our selectors, we don’t need to contort ourselves to match yesterdays browser landscape. Bravo Antti!

Want to learn more? Check out Paul Irish’s presentation on CSS performance.

ABOUT THE AUTHOR

Nicole Sullivan (@stubbornella) is an evangelist, front-end performance consultant, CSS Ninja, and author. She started the Object-Oriented CSS open source project, which answers the question: how do you scale CSS for millions of visitors or thousands of pages? She also consulted with the W3C for their beta redesign, and is the co-creator of Smush.it, an image optimization service in the cloud.

Nicole is passionate about CSS, web standards, and scalable front-end architecture for large commercial websites. She speaks about performance at conferences around the world, most recently at The Ajax Experience, ParisWeb, and Web Directions North.

She co-authored Even Faster Websites and blogs at stubbornella.org.

45 Responses to “CSS Selector Performance has changed! (For the better)”

  1. Peter Beverloo

    Is there any particular reason why this article is solely focused on WebKit, but neglects to mention that? While Antti definitely did (and still does!) great work on WebKit, sentences such as “.. there are many selectors we don’t need to worry about anymore” make it sound as if he fixed the Web rather than just WebKit.

    I feel as if this article focuses too much on WebKit’s implementation details. While optimizing CSS code to maximize style sharing and an increased hit-ratio in the ancestor filters can provide a (albeit absolutely minimal) win, both in performance as in memory usage, it’d be much more interesting to actually show how these techniques can be used. For example, “what makes WebKit select the slow-path instead of the fast-path for CSS matching” would be a proper question to ask and answer.

    My favorite quote of Antti is as follows:
    The reason descendant selectors are considered “bad” is that browser implementations have sucked. There is nothing inherently wrong with them.

    Yes, there has been made a lot of improvement by many engines, but there are still (major) gains to be made. The amount of optimization being applied to certain selectors usually reflects the real-world usage: if many people use attribute selectors, that gives engines a good reason to provide a fast path for them. While WebKit may already have addressed some CSS selector performance nits, do keep in mind that other engines may not have. Finally, the total time taken for CSS style resolution usually is near negligible for an entire page-load.

  2. Andy Davies

    Interesting…

    Is there any information on the performance of CSS selector in the mobile browsers?

    With the performance inspectors coming in WebKit and Opera, the other desktop browsers will probably follow but we really lack infomation on mobile.

    @andydavies

  3. Dave Artz

    I actually went a bit nuts when I first learned CSS selectors matched right to left – death to descendant selectors! However Steve’s study and conclusion helped bring my sanity back.

    Seems like even in older browsers this is less of an issue until you get really complex, DOM heavy (10,000+ node) pages. Your conclusion holds true even in sucky browsers, in other words.

  4. Kurt Bugeja

    Are these optimisations implemented in any rendering engine? If not, are plans to implement these optimisations scheduled?

    Thanks.

  5. My Stream » WebKit Selector Performance Changes

    [...] to think …WebKit Selector Performance Changes is a post from CSS-TricksOriginally posted from WebKit Selector Performance ChangesLeave a ReplyClick here to cancel reply.Username (required) [...]

  6. 99spaces – Freelance » WebKit Selector Performance Changes

    [...] reading here: WebKit Selector Performance Changes Click here to cancel reply. [...]

  7. WebKit Selector Performance Changes | Qtiva

    [...] Nicole Sullivan covers some of Antti Koivisto’s work on making CSS selectors faster in WebKit. Direct Link [...]

  8. TeMc

    @Peter Beverloo Wether or not WebKit specific, one should regardlessly of this never optimize for implementation details. Just make sure you write your stylesheets the way they best represent what is intended. Let browsers handle their end.

    You shouldn’t be making a single change based on reading this article, that’s not so say nothing should be changed in your stylesheet.

  9. Performance Calendar » CSS Selector Performance has changed! (For the better) « Bookmarks

    [...] Calendar » CSS Selector Performance has changed! (For the better)http://calendar.perfplanet.com/2011/css-selector-performance-has-changed-for-the-better/ [...]

  10. Matt Wilcox

    Great post, and great work, very pleased to see this as I’ve shared the idea that CSS performance is an engineers problem and not an authors for a long time :)

    A question: Can we not expose the logic behind the Style Sharing for authors to leverage? I have been asking for years to get CSS able to reference a given elements properties to apply back to the current element. That seems to be more-or-less what this is doing, but silently and behind the authors control.

    Can we not use this as a basis to form something like this:

    div.thisdiv { height: (div.otherdiv); }

    This would be incredibly useful because it means that styling can become independent of the structure of the mark-up. It’s a way to escape the limitation of the cascade. Imagine this:

    div.thisdiv { height: (div.otherdiv); }
    div.otherdiv { height: (div.thisdiv); }

    Boom, equal height divs without the need to rely on mark-up structure. We could at last link visually related elements even though they are not structurally related.

    I’d also like to see:

    div { position: (h1); top: 10px; left: 10px; }

    To be able to position elements absolutely from a different elements location. Though, that may be much more of a stretch.

  11. SPARTANPIXEL | 2012 » » WebKit Selector Performance Changes

    [...] Reading: WebKit Selector Performance Changes Tweet Share Unknown [...]

  12. WebKit Selector Performance Changes | cmasbd.com

    [...] View the original article here This entry was posted in Uncategorized and tagged Changes, Performance, Selector, WebKit by Arefin. Bookmark the permalink. [...]

  13. Dominik

    A non-recursive way of matching the selectors is probably better because recursive functions normally use much more memory and thus are slower than iterative approaches.

  14. wonderwhy-er

    @Matt Wilcox
    I always wondered why CSS does not have such features. It actually lacks many things. So far I must say that CSS is static “language”. It does not have variables, constants, bindings and reuse.
    From what I can say it seems there is opposition to making CSS more dynamic and “programming language” like. Which is sad.
    Here for example an argument in opposition of variables in CSS http://www.w3.org/People/Bos/CSS-variables
    I can’t say I agree with his arguments, but in one thing he is right. It not necessarily needs to be done in CSS itself. Some of dynamic features can be made at different level and “compiled” in to CSS as a low level language.
    I am thinking on trying out using this one http://lesscss.org

    Sadly that means that web will be not written in a form in which it then is published, which I do think defeats most of his arguments.

  15. WebKit Selector Performance Changes | techx.tk

    [...] Direct Link to Article — Permalink [...]

  16. Nate Klaiber

    @wonderwhy-er
    It has been opposed because it’s ridiculous to put that burden on the browser. Instead of being static (and fast), it would need a processor engine behind it to know how to make sense of the variables, constants, and bindings. This would not be a win for performance, it would be a step backwards.

    CSS toolkits now allow you to use the best of both worlds. In development, you get your dynamic constructs, and then it’s packaged into a static output for the browser. No extra work for the browser – it can use it’s CSS rendering as normal. It doesn’t need an engine like JavaScript (which has it’s own issues as well).

    CSS doesn’t need to be dynamic in the browser. For any client interactions you need, you have JavaScript. In the same way HTML doesn’t need to be dynamic – it’s a static markup language, and can be manipulated with JavaScript. We don’t need more overhead in the browser.

  17. Nicole Sullivan

    @peter – I focused on Webkit because they have been making some interesting improvements lately and I was excited to share. Hopefully this pushes other browser vendors to focus on selector matching performance (the way chrome pushed other browsers to improve their JS engines).

    Many pages show a substantial performance improvement if you load the page without any styles. On the fly JS updates to page content can also be slowed significantly by heavy CSS. Total rendering time is difficult to isolate and measure, but the size and complexity of the CSS file affects performance in many different ways. I wish we had better tools to isolate rendering bottlenecks.

  18. Biggs

    I think we’re all yearning for some new CSS tools to help us out. I for one would like not to get that awful feeling when a CSS file has exploded into an unmanageble monster and there’s no more time. We need more automation. Perhaps we can’t let go of our manual crafting?

  19. Web Design Weekly #26 | Web Design Weekly

    [...] CSS Selector Performance has changed! [...]

  20. * { box-sizing: border-box } FTW | 13fqcs

    [...] 90+ Page Speed scores, its way too early to be thinking about selector optimization. See also: CSS Selector Performance has changed! (For the better) by Nicole [...]

  21. * { box-sizing: border-box } FTW « Paul Irish

    [...] 90+ Page Speed scores, its way too early to be thinking about selector optimization. See also: CSS Selector Performance has changed! (For the better) by Nicole [...]

  22. Bookmarks for February 9th from 22:06 to 22:57 | gregs

    [...] Performance Calendar » CSS Selector Performance has changed! (For the better) [...]

  23. Quora

    What is the best way to organize/group CSS?…

    No, no. Minifying won’t fix what I’m talking about. If you write ul li a span {} the parser will first search for all span elements on the page, then all the a, followed by li and then ul. So if you write ul span {} instead it’ll only have to go thr…

  24. SMACSS and SASS – The future of stylesheets : Railslove

    [...] website with massive reflows, lots of old browsers and complex mobile requirements, most  sources [1, 2, 3, 4] make me believe that heavily optimizing for CSS performance, isn’t really worth the [...]

  25. Myrtis Knoebel

    This website is mostly a stroll-by means of for all of the information you wanted about this and didn’t know who to ask. Glimpse right here, and you’ll undoubtedly discover it.

  26. * { box-sizing: border-box } FTW | 糖醋素鱼

    [...] 最后根据@miketaylr的调查,我还测试了性能的开销,事实证据表明border-box对性能影响并不显著。你可能会对一般的*选择器打动肝火。很显然你已经知道他们速度缓慢。首先,不是这样的。他的速度和h1作为选择器一样快,只有在你使用特殊的如.foo>*的情况下才会变得很慢,所以不要那么用。除此之外,你不用关心通配符*的性能,除非你已经把所有的javascript代码连接在一起并放在页面的最下面,最小化了你的css和js文件,压缩所有的图片。除非这样你还是没有得到90+的网页下载速度成绩,那么思考选择器的优化还为时过早。可以参考:Nicole Sullivan写的CSS Selector Performance has changed! (For the better)。 [...]

  27. Keep your CSS selectors short — CSS Wizardry—CSS, Web Standards, Typography, and Grids by Harry Roberts

    [...] CSS selector performance is—by-and-large—not something we really need to worry about any more, but that doesn’t mean we should be wasteful. I’m sure none of use would miss losing [...]

  28. Придерживайтесь коротких CSS-селекторов

    [...] данный момент производительность CSS-селекторов — в общем и целом &m…, но это не значит, что мы должны быть расточительны. Я [...]

  29. Paul Irish

    I spoke with Paul Rouget today about this and he confirmed Selector Speed is hardly ever a concern in Firefox. This is not WebKit-specific content.
    And, because it’s interesting, the original  Dave Hyatt’s “Writing Efficient CSS” was written in 2001. (Yes, 12 years ago.0

  30. blog.oskarrough.com — CSS Selector Performance has changed

    [...] - Performance Calendar » CSS Selector Performance has changed! (For the better) [...]

  31. Chris Esplin

    The problem is too much DOM. With too much DOM, even efficient CSS patterns run slow in IE8. Chrome really doesn’t care about any of that. It has the horsepower. But those of us stuck with IE8 support requirements need to trim DOM first and worry about CSS second.

  32. 为什么排版引擎解析 CSS 选择器时一定要从右往左解析? - web前端开发 - 开发者问答

    [...] 参见:http://www.stevesouders.com/blog/2009/06/18/simplifying-css-selectors/http://calendar.perfplanet.com/2011/css-selector-performance-has-changed-for-the-better/ [...]

  33. ENGAGE Tech News | Just another WordPress site

    [...] 90+ Page Speed scores, it’s way too early to be thinking about selector optimization. See also: CSS Selector Performance has changed! (For the better) by Nicole [...]

  34. rajat raja

    I think we’re all yearning for some new CSS tools to help us out. I for one would like not to get that awful feeling when a CSS file has exploded into an unmanageble monster and there’s no more time. We need more automation. Perhaps we can’t let go of our manual crafting?

  35. * { box-sizing: border-box } FTW | InfoLogs

    […] Page Speed scores, it’s way too early to be thinking about selector optimization. See also: CSS Selector Performance has changed! (For the better) by Nicole […]

  36. CSS performance | blog.oskarrough.com

    […] CSS selector performance has changed for the better […]

  37. CSS performance – Codes & Notes

    […] CSS selector performance has changed for the better […]

  38. Andrea Baccolini | Web designer - Blog - * { Box-sizing: Border-box } FTW

    […] È veloce come h1 come selettore. Può essere lento quando si utilizza specificamente “.foo > *”, così non farlo. A parte questo, ti è permesso di preoccuparti delle prestazioni di “*” solo dopo che hai concatenato tutti i tuoi javascript, caricati in fondo, minimizzato i tuoi css e js, gzip tutti i vostri oggetti, e compresso tutte le vostre immagini senza perdita di qualità. Se non si è preso un punteggio di 90 nello Page Speed, è troppo presto per pensare all’ ottimizzazione del selettore. Vedi anche: CSS Selector performance è cambiato! (In meglio) da Nicole Sullivan. […]

  39. CSS performance revisited: selectors, bloat and expensive styles - Author and responsive web developer Ben Frain

    […] besides referencing Nicole Sullivan’s later post on Performance Calendar to back up my assumptions that the selectors used don’t really matter, I had never actually […]

  40. A Creative Grid System With Sass and calc()

    […] >. I bet some of you are gasping already. Well… yes. It’s 2014, which means CSS performance is not a problem anymore. Also, since we’re using calc(), we won’t be supporting anything below Internet Explorer 9, so […]

  41. world of warcraft

    Hi, this weekend is nice in favor of me, since this occasion i am
    reading this great informative piece of writing here at my house.

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
And here's a tool to convert HTML entities