I cannot speak for Firefox or WebKit, but at least in Chromium, these are going to be pretty much identical. Both are going to be bucketed based on the class (i.e., would only be considered for an element with class="cls", based on a hash table lookup), and then the [attr="value"] would be checked the obvious way.
The reason why the right-to-left rule makes sense _between compounds in a complex selector_ (i.e., “the parts that are separated by a space”, for an imprecise term) is that this is the part that applies to the element you are computing style for right now. I.e., say that you have a rule like
.a.b span { color: whatever; }
This rule will apply to a <span> (the span is the “subject” of the rule) that has an .a.b element somewhere up higher in the tree. If you have made the rule specific enough that it rejects on the subject (say, you're computing style right now for a <h1>, not a <span>), you can skip the entire rule right away. (Most rules do, after all, not match most elements, so rejection is what you want to optimize for.) But once that “span” selector matches on some element, you need to walk the ancestor chain from that element, potentially every single ancestor, to verify that there's no parent that has both classes .a and .b, before you can reject the rule. So rejecting on the subject (save for :has(), which is a story in its own) needs to look at one element, rejecting on something non-subject is much worse. (The Bloom filter would stop early if you don't have at least something with .a and something with .b in your tree, a so-called “fast reject”, but it can't distinguish <div class="a"><div class="b"><span> from <div class="a b"><span>.)
The best thing for selector performance _by far_ is to write the subject so that most elements don't even see it, by means of getting it into the right hash table bucket. A selector like “#xyz” is going to be _so_ much faster than “#xyz *”. But of course, most sites are not really style bound; CSS performance is something you should probably look at after you have your HTTP and JavaScript issues under control.
The reason why the right-to-left rule makes sense _between compounds in a complex selector_ (i.e., “the parts that are separated by a space”, for an imprecise term) is that this is the part that applies to the element you are computing style for right now. I.e., say that you have a rule like
This rule will apply to a <span> (the span is the “subject” of the rule) that has an .a.b element somewhere up higher in the tree. If you have made the rule specific enough that it rejects on the subject (say, you're computing style right now for a <h1>, not a <span>), you can skip the entire rule right away. (Most rules do, after all, not match most elements, so rejection is what you want to optimize for.) But once that “span” selector matches on some element, you need to walk the ancestor chain from that element, potentially every single ancestor, to verify that there's no parent that has both classes .a and .b, before you can reject the rule. So rejecting on the subject (save for :has(), which is a story in its own) needs to look at one element, rejecting on something non-subject is much worse. (The Bloom filter would stop early if you don't have at least something with .a and something with .b in your tree, a so-called “fast reject”, but it can't distinguish <div class="a"><div class="b"><span> from <div class="a b"><span>.)The best thing for selector performance _by far_ is to write the subject so that most elements don't even see it, by means of getting it into the right hash table bucket. A selector like “#xyz” is going to be _so_ much faster than “#xyz *”. But of course, most sites are not really style bound; CSS performance is something you should probably look at after you have your HTTP and JavaScript issues under control.
(I work on Chromium style performance)