Tidy up, make the cache a bit more performant.

Haven't measured a lot, will do tomorrow, but I want a test run.
This commit is contained in:
Emilio Cobos Álvarez 2016-08-10 23:17:00 -07:00
parent 3af774bd75
commit 173ec260e6
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
3 changed files with 122 additions and 70 deletions

View file

@ -7,7 +7,6 @@
use animation::Animation; use animation::Animation;
use app_units::Au; use app_units::Au;
use dom::OpaqueNode; use dom::OpaqueNode;
use dom::TElement;
use error_reporting::ParseErrorReporter; use error_reporting::ParseErrorReporter;
use euclid::Size2D; use euclid::Size2D;
use matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache}; use matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};

View file

@ -15,20 +15,18 @@ use data::PrivateStyleData;
use dom::{TElement, TNode, TRestyleDamage, UnsafeNode}; use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
use properties::longhands::display::computed_value as display; use properties::longhands::display::computed_value as display;
use properties::{ComputedValues, PropertyDeclaration, cascade}; use properties::{ComputedValues, PropertyDeclaration, cascade};
use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement}; use selector_impl::{TheSelectorImpl, PseudoElement};
use selector_matching::{DeclarationBlock, Stylist}; use selector_matching::{DeclarationBlock, Stylist};
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::{StyleRelations, AFFECTED_BY_PSEUDO_ELEMENTS}; use selectors::matching::{StyleRelations, AFFECTED_BY_PSEUDO_ELEMENTS};
use selectors::{Element, MatchAttr}; use selectors::{Element, MatchAttr};
use sink::ForgetfulSink; use sink::ForgetfulSink;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt;
use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::slice::Iter; use std::slice::Iter;
use std::sync::Arc; use std::sync::Arc;
use string_cache::{Atom, Namespace}; use string_cache::Atom;
use traversal::RestyleResult; use traversal::RestyleResult;
use util::opts; use util::opts;
@ -180,6 +178,27 @@ impl ApplicableDeclarationsCache {
} }
} }
/// Information regarding a candidate.
///
/// TODO: We can stick a lot more info here.
#[derive(Debug)]
struct StyleSharingCandidate {
/// The node, guaranteed to be an element.
node: UnsafeNode,
/// The cached computed style, here for convenience.
style: Arc<ComputedValues>,
/// The cached common style affecting attribute info.
common_style_affecting_attributes: CommonStyleAffectingAttributes,
}
impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
fn eq(&self, other: &Self) -> bool {
self.node == other.node &&
arc_ptr_eq(&self.style, &other.style) &&
self.common_style_affecting_attributes == other.common_style_affecting_attributes
}
}
/// An LRU cache of the last few nodes seen, so that we can aggressively try to /// An LRU cache of the last few nodes seen, so that we can aggressively try to
/// reuse their styles. /// reuse their styles.
/// ///
@ -191,7 +210,7 @@ impl ApplicableDeclarationsCache {
/// difficulty, but good luck with layout and all the types with assoc. /// difficulty, but good luck with layout and all the types with assoc.
/// lifetimes). /// lifetimes).
pub struct StyleSharingCandidateCache { pub struct StyleSharingCandidateCache {
cache: LRUCache<UnsafeNode, ()>, cache: LRUCache<StyleSharingCandidate, ()>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -211,7 +230,8 @@ pub enum CacheMiss {
} }
fn element_matches_candidate<E: TElement>(element: &E, fn element_matches_candidate<E: TElement>(element: &E,
candidate: &E, candidate: &StyleSharingCandidate,
candidate_element: &E,
shared_context: &SharedStyleContext) shared_context: &SharedStyleContext)
-> Result<Arc<ComputedValues>, CacheMiss> { -> Result<Arc<ComputedValues>, CacheMiss> {
macro_rules! miss { macro_rules! miss {
@ -220,23 +240,23 @@ fn element_matches_candidate<E: TElement>(element: &E,
} }
} }
if element.parent_element() != candidate.parent_element() { if element.parent_element() != candidate_element.parent_element() {
miss!(Parent) miss!(Parent)
} }
if *element.get_local_name() != *candidate.get_local_name() { if *element.get_local_name() != *candidate_element.get_local_name() {
miss!(LocalName) miss!(LocalName)
} }
if *element.get_namespace() != *candidate.get_namespace() { if *element.get_namespace() != *candidate_element.get_namespace() {
miss!(Namespace) miss!(Namespace)
} }
if element.is_link() != candidate.is_link() { if element.is_link() != candidate_element.is_link() {
miss!(Link) miss!(Link)
} }
if element.get_state() != candidate.get_state() { if element.get_state() != candidate_element.get_state() {
miss!(State) miss!(State)
} }
@ -248,7 +268,7 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(StyleAttr) miss!(StyleAttr)
} }
if !have_same_class(element, candidate) { if !have_same_class(element, candidate_element) {
miss!(Class) miss!(Class)
} }
@ -256,34 +276,35 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(CommonStyleAffectingAttributes) miss!(CommonStyleAffectingAttributes)
} }
if !have_same_presentational_hints(element, candidate) { if !have_same_presentational_hints(element, candidate_element) {
miss!(PresHints) miss!(PresHints)
} }
if !match_same_sibling_affecting_rules(element, candidate, shared_context) { if !match_same_sibling_affecting_rules(element,
candidate_element,
shared_context) {
miss!(SiblingRules) miss!(SiblingRules)
} }
if !match_same_not_common_style_affecting_attributes_rules(element, candidate, shared_context) { if !match_same_not_common_style_affecting_attributes_rules(element,
candidate_element,
shared_context) {
miss!(NonCommonAttrRules) miss!(NonCommonAttrRules)
} }
let candidate_node = candidate.as_node(); Ok(candidate.style.clone())
let candidate_style = candidate_node.borrow_data().unwrap().style.as_ref().unwrap().clone();
Ok(candidate_style)
} }
fn have_same_common_style_affecting_attributes<E: TElement>(element: &E, fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
candidate: &E) -> bool { candidate: &StyleSharingCandidate) -> bool {
// XXX probably could do something smarter. Also, the cache should // XXX probably could do something smarter. Also, the cache should
// precompute this for the parent. Just experimenting now though. // precompute this for the parent. Just experimenting now though.
create_common_style_affecting_attributes_from_element(element) == create_common_style_affecting_attributes_from_element(element) ==
create_common_style_affecting_attributes_from_element(candidate) candidate.common_style_affecting_attributes
} }
fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool { fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool {
let mut first = vec![]; let mut first = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut first); element.synthesize_presentational_hints_for_legacy_attributes(&mut first);
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
let mut second = vec![]; let mut second = vec![];
@ -361,14 +382,15 @@ fn have_same_class<E: TElement>(element: &E, candidate: &E) -> bool {
first == second first == second
} }
// TODO: These re-match the candidate every time, which is suboptimal.
#[inline]
fn match_same_not_common_style_affecting_attributes_rules<E: TElement>(element: &E, fn match_same_not_common_style_affecting_attributes_rules<E: TElement>(element: &E,
candidate: &E, candidate: &E,
ctx: &SharedStyleContext) -> bool { ctx: &SharedStyleContext) -> bool {
// XXX Same here, could store in the cache an index with the matched rules,
// for example.
ctx.stylist.match_same_not_common_style_affecting_attributes_rules(element, candidate) ctx.stylist.match_same_not_common_style_affecting_attributes_rules(element, candidate)
} }
#[inline]
fn match_same_sibling_affecting_rules<E: TElement>(element: &E, fn match_same_sibling_affecting_rules<E: TElement>(element: &E,
candidate: &E, candidate: &E,
ctx: &SharedStyleContext) -> bool { ctx: &SharedStyleContext) -> bool {
@ -384,14 +406,13 @@ impl StyleSharingCandidateCache {
} }
} }
pub fn iter(&self) -> Iter<(UnsafeNode, ())> { fn iter(&self) -> Iter<(StyleSharingCandidate, ())> {
self.cache.iter() self.cache.iter()
} }
pub fn insert_if_possible<E: TElement>(&mut self, pub fn insert_if_possible<E: TElement>(&mut self,
element: &E, element: &E,
relations: StyleRelations) { relations: StyleRelations) {
use selectors::matching::*; // For flags
use traversal::relations_are_shareable; use traversal::relations_are_shareable;
let parent = match element.parent_element() { let parent = match element.parent_element() {
@ -409,11 +430,30 @@ impl StyleSharingCandidateCache {
return; return;
} }
// XXX check transitions/animations and reject! let node = element.as_node();
let data = node.borrow_data().unwrap();
let style = data.style.as_ref().unwrap();
let box_style = style.get_box();
if box_style.transition_property_count() > 0 {
debug!("Failing to insert to the cache: transitions");
return;
}
if box_style.animation_name_count() > 0 {
debug!("Failing to insert to the cache: animations");
return;
}
debug!("Inserting into cache: {:?} with parent {:?}", debug!("Inserting into cache: {:?} with parent {:?}",
element.as_node().to_unsafe(), parent.as_node().to_unsafe()); element.as_node().to_unsafe(), parent.as_node().to_unsafe());
self.cache.insert(element.as_node().to_unsafe(), ()) self.cache.insert(StyleSharingCandidate {
node: node.to_unsafe(),
style: style.clone(),
common_style_affecting_attributes:
create_common_style_affecting_attributes_from_element(element),
}, ());
} }
pub fn touch(&mut self, index: usize) { pub fn touch(&mut self, index: usize) {
@ -580,17 +620,16 @@ trait PrivateElementMatchMethods: TElement {
fn share_style_with_candidate_if_possible(&self, fn share_style_with_candidate_if_possible(&self,
parent_node: Self::ConcreteNode, parent_node: Self::ConcreteNode,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
candidate: &Self) candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>> { -> Result<Arc<ComputedValues>, CacheMiss> {
debug_assert!(parent_node.is_element()); debug_assert!(parent_node.is_element());
match element_matches_candidate(self, candidate, shared_context) { let candidate_element = unsafe {
Ok(cv) => Some(cv), Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
Err(error) => { };
debug!("Cache miss: {:?}", error);
None element_matches_candidate(self, candidate, &candidate_element,
} shared_context)
}
} }
} }
@ -658,40 +697,55 @@ pub trait ElementMatchMethods : TElement {
_ => return StyleSharingResult::CannotShare, _ => return StyleSharingResult::CannotShare,
}; };
let iter = style_sharing_candidate_cache.iter().map(|&(unsafe_node, ())| { for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
Self::ConcreteNode::from_unsafe(&unsafe_node).as_element().unwrap() let sharing_result = self.share_style_with_candidate_if_possible(parent,
}); shared_context,
candidate);
match sharing_result {
Ok(shared_style) => {
// Yay, cache hit. Share the style.
let node = self.as_node();
let style = &mut node.mutate_data().unwrap().style;
for (i, candidate) in iter.enumerate() { // TODO: add the display: none optimisation here too! Even
if let Some(shared_style) = self.share_style_with_candidate_if_possible(parent, // better, factor it out/make it a bit more generic so Gecko
shared_context, // can decide more easily if it knows that it's a child of
&candidate) { // replaced content, or similar stuff!
// Yay, cache hit. Share the style. let damage =
let node = self.as_node(); match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
Some(ref source) => {
<<Self as TElement>::ConcreteNode as TNode>
::ConcreteRestyleDamage::compute(source, &shared_style)
}
None => {
<<Self as TElement>::ConcreteNode as TNode>
::ConcreteRestyleDamage::rebuild_and_reflow()
}
};
let style = &mut node.mutate_data().unwrap().style; let restyle_result = if shared_style.get_box().clone_display() == display::T::none {
RestyleResult::Stop
let damage = } else {
match node.existing_style_for_restyle_damage((*style).as_ref(), None) { RestyleResult::Continue
Some(ref source) => {
<<Self as TElement>::ConcreteNode as TNode>
::ConcreteRestyleDamage::compute(source, &shared_style)
}
None => {
<<Self as TElement>::ConcreteNode as TNode>
::ConcreteRestyleDamage::rebuild_and_reflow()
}
}; };
let restyle_result = if shared_style.get_box().clone_display() == display::T::none { *style = Some(shared_style);
RestyleResult::Stop
} else {
RestyleResult::Continue
};
*style = Some(shared_style); return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
}
return StyleSharingResult::StyleWasShared(i, damage, restyle_result) Err(miss) => {
// Cache miss, let's see what kind of failure to decide
// whether we keep trying or not.
match miss {
// Too expensive failure, give up, we don't want another
// one of these.
CacheMiss::CommonStyleAffectingAttributes |
CacheMiss::PresHints |
CacheMiss::SiblingRules |
CacheMiss::NonCommonAttrRules => break,
_ => {}
}
}
} }
} }

View file

@ -11,7 +11,6 @@ use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleS
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::StyleRelations; use selectors::matching::StyleRelations;
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use tid::tid; use tid::tid;
use util::opts; use util::opts;
@ -367,8 +366,8 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
// Perform the CSS cascade. // Perform the CSS cascade.
unsafe { unsafe {
restyle_result = node.cascade_node(context, restyle_result = node.cascade_node(context,
parent_opt, parent_opt,
&applicable_declarations); &applicable_declarations);
} }
// Add ourselves to the LRU cache. // Add ourselves to the LRU cache.