From 3e80f482ba99a8c47fc64b77ac9aa3a55c728cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 19 Aug 2016 23:16:15 -0700 Subject: [PATCH] style: Don't loop over all the set of dependencies always. The dependency count is not at all minor, and this way we avoid looping through all of them in the common cases, mainly either changing state, or attributes. --- components/style/restyle_hints.rs | 116 ++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 22 deletions(-) diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 2a9ca8b0569..d5ccffead15 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -5,6 +5,8 @@ //! Restyle hints: an optimization to avoid unnecessarily matching selectors. use element_state::*; +#[cfg(feature = "servo")] +use heapsize::HeapSizeOf; use selector_impl::{ElementExt, TheSelectorImpl, NonTSPseudoClass, AttrValue}; use selectors::matching::StyleRelations; use selectors::matching::matches_complex_selector; @@ -33,6 +35,11 @@ bitflags! { } } +#[cfg(feature = "servo")] +impl HeapSizeOf for RestyleHint { + fn heap_size_of_children(&self) -> usize { 0 } +} + /// In order to compute restyle hints, we perform a selector match against a /// list of partial selectors whose rightmost simple selector may be sensitive /// to the thing being changed. We do this matching twice, once for the element @@ -334,23 +341,50 @@ impl Sensitivities { #[cfg_attr(feature = "servo", derive(HeapSizeOf))] struct Dependency { selector: Arc>, - combinator: Option, + hint: RestyleHint, sensitivities: Sensitivities, } +/// A set of dependencies for a given stylist. +/// +/// Note that there are measurable perf wins from storing them separately +/// depending on what kind of change they affect, and its also not a big deal to +/// do it, since the dependencies are per-document. #[derive(Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct DependencySet { - deps: Vec, + /// Dependencies only affected by state. + state_deps: Vec, + /// Dependencies only affected by attributes. + attr_deps: Vec, + /// Dependencies affected by both. + common_deps: Vec, } impl DependencySet { + fn add_dependency(&mut self, dep: Dependency) { + let affects_attrs = dep.sensitivities.attrs; + let affects_states = !dep.sensitivities.states.is_empty(); + + if affects_attrs && affects_states { + self.common_deps.push(dep) + } else if affects_attrs { + self.attr_deps.push(dep) + } else { + self.state_deps.push(dep) + } + } + pub fn new() -> Self { - DependencySet { deps: Vec::new() } + DependencySet { + state_deps: vec![], + attr_deps: vec![], + common_deps: vec![], + } } pub fn len(&self) -> usize { - self.deps.len() + self.common_deps.len() + self.attr_deps.len() + self.state_deps.len() } pub fn note_selector(&mut self, selector: &Arc>) { @@ -365,9 +399,9 @@ impl DependencySet { } } if !sensitivities.is_empty() { - self.deps.push(Dependency { + self.add_dependency(Dependency { selector: cur.clone(), - combinator: combinator, + hint: combinator_to_restyle_hint(combinator), sensitivities: sensitivities, }); } @@ -383,38 +417,76 @@ impl DependencySet { } pub fn clear(&mut self) { - self.deps.clear(); + self.common_deps.clear(); + self.attr_deps.clear(); + self.state_deps.clear(); } -} -impl DependencySet { pub fn compute_hint(&self, el: &E, snapshot: &E::Snapshot, current_state: ElementState) -> RestyleHint - where E: ElementExt + Clone + where E: ElementExt + Clone { debug!("About to calculate restyle hint for element. Deps: {}", - self.deps.len()); + self.len()); - let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state); + let state_changes = snapshot.state() + .map_or_else(ElementState::empty, |old_state| current_state ^ old_state); let attrs_changed = snapshot.has_attrs(); + + if state_changes.is_empty() && !attrs_changed { + return RestyleHint::empty(); + } + let mut hint = RestyleHint::empty(); - for dep in &self.deps { - if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) { - let old_el: ElementWrapper = ElementWrapper::new_with_snapshot(el.clone(), snapshot); + let snapshot = ElementWrapper::new_with_snapshot(el.clone(), snapshot); + + Self::compute_partial_hint(&self.common_deps, el, &snapshot, + &state_changes, attrs_changed, &mut hint); + + if !state_changes.is_empty() { + Self::compute_partial_hint(&self.state_deps, el, &snapshot, + &state_changes, attrs_changed, &mut hint); + } + + if attrs_changed { + Self::compute_partial_hint(&self.attr_deps, el, &snapshot, + &state_changes, attrs_changed, &mut hint); + } + + hint + } + + fn compute_partial_hint(deps: &[Dependency], + element: &E, + snapshot: &ElementWrapper, + state_changes: &ElementState, + attrs_changed: bool, + hint: &mut RestyleHint) + where E: ElementExt + { + if hint.is_all() { + return; + } + for dep in deps { + debug_assert!(state_changes.intersects(dep.sensitivities.states) || + attrs_changed && dep.sensitivities.attrs, + "Testing a completely ineffective dependency?"); + if !hint.intersects(dep.hint) { let matched_then = - matches_complex_selector(&*dep.selector, &old_el, None, &mut StyleRelations::empty()); + matches_complex_selector(&dep.selector, snapshot, None, + &mut StyleRelations::empty()); let matches_now = - matches_complex_selector(&*dep.selector, el, None, &mut StyleRelations::empty()); + matches_complex_selector(&dep.selector, element, None, + &mut StyleRelations::empty()); if matched_then != matches_now { - hint.insert(combinator_to_restyle_hint(dep.combinator)); - if hint.is_all() { - break - } + hint.insert(dep.hint); + } + if hint.is_all() { + break; } } } - hint } }