style: Eliminate a temporary vector when performing selector matching.

26% improvement in style recalc on the rainbow page.
This commit is contained in:
Patrick Walton 2014-02-07 21:17:47 -08:00
parent 0fa0940ce9
commit 0b894113e9
4 changed files with 84 additions and 95 deletions

View file

@ -16,12 +16,12 @@ use servo_util::namespace::Null;
use servo_util::smallvec::{SmallVec, SmallVec0, SmallVec16}; use servo_util::smallvec::{SmallVec, SmallVec0, SmallVec16};
use std::cast; use std::cast;
use std::to_bytes; use std::to_bytes;
use style::{After, Before, ComputedValues, PropertyDeclaration, Stylist, TNode, cascade}; use style::{After, Before, ComputedValues, MatchedProperty, Stylist, TNode, cascade};
pub struct ApplicableDeclarations { pub struct ApplicableDeclarations {
normal: SmallVec16<Arc<~[PropertyDeclaration]>>, normal: SmallVec16<MatchedProperty>,
before: SmallVec0<Arc<~[PropertyDeclaration]>>, before: SmallVec0<MatchedProperty>,
after: SmallVec0<Arc<~[PropertyDeclaration]>>, after: SmallVec0<MatchedProperty>,
} }
impl ApplicableDeclarations { impl ApplicableDeclarations {
@ -42,11 +42,11 @@ impl ApplicableDeclarations {
#[deriving(Clone)] #[deriving(Clone)]
struct ApplicableDeclarationsCacheEntry { struct ApplicableDeclarationsCacheEntry {
declarations: SmallVec16<Arc<~[PropertyDeclaration]>>, declarations: SmallVec16<MatchedProperty>,
} }
impl ApplicableDeclarationsCacheEntry { impl ApplicableDeclarationsCacheEntry {
fn new(slice: &[Arc<~[PropertyDeclaration]>]) -> ApplicableDeclarationsCacheEntry { fn new(slice: &[MatchedProperty]) -> ApplicableDeclarationsCacheEntry {
let mut entry_declarations = SmallVec16::new(); let mut entry_declarations = SmallVec16::new();
for declarations in slice.iter() { for declarations in slice.iter() {
entry_declarations.push(declarations.clone()); entry_declarations.push(declarations.clone());
@ -71,12 +71,11 @@ impl IterBytes for ApplicableDeclarationsCacheEntry {
} }
struct ApplicableDeclarationsCacheQuery<'a> { struct ApplicableDeclarationsCacheQuery<'a> {
declarations: &'a [Arc<~[PropertyDeclaration]>], declarations: &'a [MatchedProperty],
} }
impl<'a> ApplicableDeclarationsCacheQuery<'a> { impl<'a> ApplicableDeclarationsCacheQuery<'a> {
fn new(declarations: &'a [Arc<~[PropertyDeclaration]>]) fn new(declarations: &'a [MatchedProperty]) -> ApplicableDeclarationsCacheQuery<'a> {
-> ApplicableDeclarationsCacheQuery<'a> {
ApplicableDeclarationsCacheQuery { ApplicableDeclarationsCacheQuery {
declarations: declarations, declarations: declarations,
} }
@ -128,16 +127,14 @@ impl ApplicableDeclarationsCache {
} }
} }
fn find(&self, declarations: &[Arc<~[PropertyDeclaration]>]) -> Option<Arc<ComputedValues>> { fn find(&self, declarations: &[MatchedProperty]) -> Option<Arc<ComputedValues>> {
match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) { match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
None => None, None => None,
Some(ref values) => Some((*values).clone()), Some(ref values) => Some((*values).clone()),
} }
} }
fn insert(&mut self, fn insert(&mut self, declarations: &[MatchedProperty], style: Arc<ComputedValues>) {
declarations: &[Arc<~[PropertyDeclaration]>],
style: Arc<ComputedValues>) {
drop(self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)) drop(self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style))
} }
} }
@ -164,7 +161,7 @@ pub trait MatchMethods {
trait PrivateMatchMethods { trait PrivateMatchMethods {
fn cascade_node_pseudo_element(&self, fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>, parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[Arc<~[PropertyDeclaration]>], applicable_declarations: &[MatchedProperty],
style: &mut Option<Arc<ComputedValues>>, style: &mut Option<Arc<ComputedValues>>,
initial_values: &ComputedValues, initial_values: &ComputedValues,
applicable_declarations_cache: &mut applicable_declarations_cache: &mut
@ -174,7 +171,7 @@ trait PrivateMatchMethods {
impl<'ln> PrivateMatchMethods for LayoutNode<'ln> { impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
fn cascade_node_pseudo_element(&self, fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>, parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[Arc<~[PropertyDeclaration]>], applicable_declarations: &[MatchedProperty],
style: &mut Option<Arc<ComputedValues>>, style: &mut Option<Arc<ComputedValues>>,
initial_values: &ComputedValues, initial_values: &ComputedValues,
applicable_declarations_cache: &mut applicable_declarations_cache: &mut

View file

@ -13,6 +13,7 @@ pub use cssparser::ast::*;
use errors::{ErrorLoggerIterator, log_css_error}; use errors::{ErrorLoggerIterator, log_css_error};
pub use parsing_utils::*; pub use parsing_utils::*;
pub use self::common_types::*; pub use self::common_types::*;
use selector_matching::MatchedProperty;
pub mod common_types; pub mod common_types;
@ -1174,7 +1175,7 @@ fn cascade_with_cached_declarations(applicable_declarations: &[Arc<~[PropertyDec
<%def name="apply_cached(priority)"> <%def name="apply_cached(priority)">
for sub_list in applicable_declarations.iter() { for sub_list in applicable_declarations.iter() {
for declaration in sub_list.get().iter() { for declaration in sub_list.declarations.get().iter() {
match declaration { match declaration {
% for style_struct in STYLE_STRUCTS: % for style_struct in STYLE_STRUCTS:
% if style_struct.inherited: % if style_struct.inherited:
@ -1243,7 +1244,7 @@ fn cascade_with_cached_declarations(applicable_declarations: &[Arc<~[PropertyDec
/// this is ignored. /// this is ignored.
/// ///
/// Returns the computed values and a boolean indicating whether the result is cacheable. /// Returns the computed values and a boolean indicating whether the result is cacheable.
pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], pub fn cascade(applicable_declarations: &[MatchedProperty],
parent_style: Option< &ComputedValues >, parent_style: Option< &ComputedValues >,
initial_values: &ComputedValues, initial_values: &ComputedValues,
cached_style: Option< &ComputedValues >) cached_style: Option< &ComputedValues >)
@ -1289,7 +1290,7 @@ pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>],
let mut cacheable = true; let mut cacheable = true;
<%def name="apply(needed_for_context)"> <%def name="apply(needed_for_context)">
for sub_list in applicable_declarations.iter() { for sub_list in applicable_declarations.iter() {
for declaration in sub_list.get().iter() { for declaration in sub_list.declarations.get().iter() {
match declaration { match declaration {
% for style_struct in STYLE_STRUCTS: % for style_struct in STYLE_STRUCTS:
% for property in style_struct.longhands: % for property in style_struct.longhands:

View file

@ -9,7 +9,7 @@ use std::str;
use std::to_bytes; use std::to_bytes;
use servo_util::namespace; use servo_util::namespace;
use servo_util::smallvec::{SmallVec, SmallVec16}; use servo_util::smallvec::SmallVec;
use servo_util::sort; use servo_util::sort;
use media_queries::{Device, Screen}; use media_queries::{Device, Screen};
@ -104,10 +104,11 @@ impl SelectorMap {
/// Extract matching rules as per node's ID, classes, tag name, etc.. /// Extract matching rules as per node's ID, classes, tag name, etc..
/// Sort the Rules at the end to maintain cascading order. /// Sort the Rules at the end to maintain cascading order.
fn get_all_matching_rules<E:TElement, fn get_all_matching_rules<E:TElement,
N:TNode<E>>( N:TNode<E>,
V:SmallVec<MatchedProperty>>(
&self, &self,
node: &N, node: &N,
matching_rules_list: &mut SmallVec16<Rule>) { matching_rules_list: &mut V) {
if self.empty { if self.empty {
return return
} }
@ -153,11 +154,12 @@ impl SelectorMap {
} }
fn get_matching_rules_from_hash<E:TElement, fn get_matching_rules_from_hash<E:TElement,
N:TNode<E>>( N:TNode<E>,
V:SmallVec<MatchedProperty>>(
node: &N, node: &N,
hash: &HashMap<~str,~[Rule]>, hash: &HashMap<~str,~[Rule]>,
key: &str, key: &str,
matching_rules: &mut SmallVec16<Rule>) { matching_rules: &mut V) {
match hash.find_equiv(&key) { match hash.find_equiv(&key) {
Some(rules) => { Some(rules) => {
SelectorMap::get_matching_rules(node, *rules, matching_rules) SelectorMap::get_matching_rules(node, *rules, matching_rules)
@ -167,11 +169,12 @@ impl SelectorMap {
} }
fn get_matching_rules_from_hash_ignoring_case<E:TElement, fn get_matching_rules_from_hash_ignoring_case<E:TElement,
N:TNode<E>>( N:TNode<E>,
V:SmallVec<MatchedProperty>>(
node: &N, node: &N,
hash: &HashMap<~str,~[Rule]>, hash: &HashMap<~str,~[Rule]>,
key: &str, key: &str,
matching_rules: &mut SmallVec16<Rule>) { matching_rules: &mut V) {
match hash.find_equiv(&LowercaseAsciiString(key)) { match hash.find_equiv(&LowercaseAsciiString(key)) {
Some(rules) => { Some(rules) => {
SelectorMap::get_matching_rules(node, *rules, matching_rules) SelectorMap::get_matching_rules(node, *rules, matching_rules)
@ -182,14 +185,15 @@ impl SelectorMap {
/// Adds rules in `rules` that match `node` to the `matching_rules` list. /// Adds rules in `rules` that match `node` to the `matching_rules` list.
fn get_matching_rules<E:TElement, fn get_matching_rules<E:TElement,
N:TNode<E>>( N:TNode<E>,
V:SmallVec<MatchedProperty>>(
node: &N, node: &N,
rules: &[Rule], rules: &[Rule],
matching_rules: &mut SmallVec16<Rule>) { matching_rules: &mut V) {
for rule in rules.iter() { for rule in rules.iter() {
if matches_compound_selector(rule.selector.get(), node) { if matches_compound_selector(rule.selector.get(), node) {
// TODO(pradeep): Is the cloning inefficient? // TODO(pradeep): Is the cloning inefficient?
matching_rules.push(rule.clone()); matching_rules.push(rule.property.clone());
} }
} }
} }
@ -339,9 +343,11 @@ impl Stylist {
}; };
map.$priority.insert(Rule { map.$priority.insert(Rule {
selector: selector.compound_selectors.clone(), selector: selector.compound_selectors.clone(),
property: MatchedProperty {
specificity: selector.specificity, specificity: selector.specificity,
declarations: style_rule.declarations.$priority.clone(), declarations: style_rule.declarations.$priority.clone(),
source_order: self.rules_source_order, source_order: self.rules_source_order,
},
}); });
} }
} }
@ -360,7 +366,7 @@ impl Stylist {
/// `ElementRuleCollector` in WebKit. /// `ElementRuleCollector` in WebKit.
pub fn push_applicable_declarations<E:TElement, pub fn push_applicable_declarations<E:TElement,
N:TNode<E>, N:TNode<E>,
V:SmallVec<Arc<~[PropertyDeclaration]>>>( V:SmallVec<MatchedProperty>>(
&self, &self,
element: &N, element: &N,
style_attribute: Option<&PropertyDeclarationBlock>, style_attribute: Option<&PropertyDeclarationBlock>,
@ -375,63 +381,28 @@ impl Stylist {
Some(Before) => &self.before_map, Some(Before) => &self.before_map,
Some(After) => &self.after_map, Some(After) => &self.after_map,
}; };
// In cascading order:
let rule_map_list = [
&map.user_agent.normal,
&map.user.normal,
&map.author.normal,
&map.author.important,
&map.user.important,
&map.user_agent.important
];
// We keep track of the indices of each of the rule maps in the list we're building so that
// we have the indices straight at the end.
let mut rule_map_indices = [ 0, ..6 ];
let mut matching_rules_list = SmallVec16::new();
for (i, rule_map) in rule_map_list.iter().enumerate() {
rule_map_indices[i] = matching_rules_list.len();
rule_map.get_all_matching_rules(element, &mut matching_rules_list);
}
let count = matching_rules_list.len();
let mut declaration_iter = matching_rules_list.move_iter().map(|rule| {
let Rule {
declarations,
..
} = rule;
declarations
});
// Gather up all rules.
let mut i = 0;
// Step 1: Normal rules. // Step 1: Normal rules.
while i < rule_map_indices[3] { map.user_agent.normal.get_all_matching_rules(element, applicable_declarations);
applicable_declarations.push(declaration_iter.next().unwrap()); map.user.normal.get_all_matching_rules(element, applicable_declarations);
i += 1 map.author.normal.get_all_matching_rules(element, applicable_declarations);
}
// Step 2: Normal style attributes. // Step 2: Normal style attributes.
style_attribute.map(|sa| applicable_declarations.push(sa.normal.clone())); style_attribute.map(|sa| {
applicable_declarations.push(MatchedProperty::from_declarations(sa.normal.clone()))
});
// Step 3: Author-supplied `!important` rules. // Step 3: Author-supplied `!important` rules.
while i < rule_map_indices[4] { map.author.important.get_all_matching_rules(element, applicable_declarations);
applicable_declarations.push(declaration_iter.next().unwrap());
i += 1
}
// Step 4: `!important` style attributes. // Step 4: `!important` style attributes.
style_attribute.map(|sa| applicable_declarations.push(sa.important.clone())); style_attribute.map(|sa| {
applicable_declarations.push(MatchedProperty::from_declarations(sa.important.clone()))
});
// Step 5: User and UA `!important` rules. // Step 5: User and UA `!important` rules.
while i < count { map.user.important.get_all_matching_rules(element, applicable_declarations);
applicable_declarations.push(declaration_iter.next().unwrap()); map.user_agent.important.get_all_matching_rules(element, applicable_declarations);
i += 1
}
} }
} }
@ -473,30 +444,47 @@ struct Rule {
// that it matches. Selector contains an owned vector (through // that it matches. Selector contains an owned vector (through
// CompoundSelector) and we want to avoid the allocation. // CompoundSelector) and we want to avoid the allocation.
selector: Arc<CompoundSelector>, selector: Arc<CompoundSelector>,
property: MatchedProperty,
}
/// A property declaration together with its precedence among rules of equal specificity so that
/// we can sort them.
#[deriving(Clone)]
pub struct MatchedProperty {
declarations: Arc<~[PropertyDeclaration]>, declarations: Arc<~[PropertyDeclaration]>,
// Precedence among rules of equal specificity
source_order: uint, source_order: uint,
specificity: u32, specificity: u32,
} }
impl Ord for Rule { impl MatchedProperty {
#[inline] #[inline]
fn lt(&self, other: &Rule) -> bool { pub fn from_declarations(declarations: Arc<~[PropertyDeclaration]>) -> MatchedProperty {
let this_rank = (self.specificity, self.source_order); MatchedProperty {
let other_rank = (other.specificity, other.source_order); declarations: declarations,
this_rank < other_rank source_order: 0,
specificity: 0,
}
} }
} }
impl Eq for Rule { impl Eq for MatchedProperty {
#[inline] #[inline]
fn eq(&self, other: &Rule) -> bool { fn eq(&self, other: &MatchedProperty) -> bool {
let this_rank = (self.specificity, self.source_order); let this_rank = (self.specificity, self.source_order);
let other_rank = (other.specificity, other.source_order); let other_rank = (other.specificity, other.source_order);
this_rank == other_rank this_rank == other_rank
} }
} }
impl Ord for MatchedProperty {
#[inline]
fn lt(&self, other: &MatchedProperty) -> bool {
let this_rank = (self.specificity, self.source_order);
let other_rank = (other.specificity, other.source_order);
this_rank < other_rank
}
}
fn matches_compound_selector<E:TElement,N:TNode<E>>(selector: &CompoundSelector, element: &N) fn matches_compound_selector<E:TElement,N:TNode<E>>(selector: &CompoundSelector, element: &N)
-> bool { -> bool {
if !selector.simple_selectors.iter().all(|simple_selector| { if !selector.simple_selectors.iter().all(|simple_selector| {
@ -765,7 +753,7 @@ fn matches_last_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use extra::arc::Arc; use extra::arc::Arc;
use super::{Rule, SelectorMap}; use super::{MatchedProperty, Rule, SelectorMap};
/// Helper method to get some Rules from selector strings. /// Helper method to get some Rules from selector strings.
/// Each sublist of the result contains the Rules for one StyleRule. /// Each sublist of the result contains the Rules for one StyleRule.
@ -779,11 +767,13 @@ mod tests {
parse_selector_list(tokenize(*selectors).map(|(c, _)| c).to_owned_vec(), &namespaces) parse_selector_list(tokenize(*selectors).map(|(c, _)| c).to_owned_vec(), &namespaces)
.unwrap().move_iter().map(|s| { .unwrap().move_iter().map(|s| {
Rule { Rule {
selector: s.compound_selectors.clone(),
property: MatchedProperty {
specificity: s.specificity, specificity: s.specificity,
selector: s.compound_selectors,
declarations: Arc::new(~[]), declarations: Arc::new(~[]),
source_order: i, source_order: i,
} }
}
}).to_owned_vec() }).to_owned_vec()
}).to_owned_vec() }).to_owned_vec()
} }
@ -793,7 +783,7 @@ mod tests {
let rules_list = get_mock_rules(["a.intro", "img.sidebar"]); let rules_list = get_mock_rules(["a.intro", "img.sidebar"]);
let rule1 = rules_list[0][0].clone(); let rule1 = rules_list[0][0].clone();
let rule2 = rules_list[1][0].clone(); let rule2 = rules_list[1][0].clone();
assert!(rule1 < rule2, "The rule that comes later should win."); assert!(rule1.property < rule2.property, "The rule that comes later should win.");
} }
#[test] #[test]
@ -824,9 +814,9 @@ mod tests {
let rules_list = get_mock_rules([".intro.foo", "#top"]); let rules_list = get_mock_rules([".intro.foo", "#top"]);
let mut selector_map = SelectorMap::new(); let mut selector_map = SelectorMap::new();
selector_map.insert(rules_list[1][0].clone()); selector_map.insert(rules_list[1][0].clone());
assert_eq!(1, selector_map.id_hash.find(&~"top").unwrap()[0].source_order); assert_eq!(1, selector_map.id_hash.find(&~"top").unwrap()[0].property.source_order);
selector_map.insert(rules_list[0][0].clone()); selector_map.insert(rules_list[0][0].clone());
assert_eq!(0, selector_map.class_hash.find(&~"intro").unwrap()[0].source_order); assert_eq!(0, selector_map.class_hash.find(&~"intro").unwrap()[0].property.source_order);
assert!(selector_map.class_hash.find(&~"foo").is_none()); assert!(selector_map.class_hash.find(&~"foo").is_none());
} }
} }

View file

@ -19,6 +19,7 @@ extern mod servo_util = "util";
// Public API // Public API
pub use stylesheets::Stylesheet; pub use stylesheets::Stylesheet;
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin}; pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
pub use selector_matching::{MatchedProperty};
pub use properties::{cascade, PropertyDeclaration, ComputedValues, computed_values}; pub use properties::{cascade, PropertyDeclaration, ComputedValues, computed_values};
pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
pub use properties::{initial_values}; pub use properties::{initial_values};