Cache the results of cache entry revalidation and use the bloom filter.

This commit is contained in:
Bobby Holley 2017-04-11 14:23:35 +08:00
parent d51d95d0c7
commit 1663bf6e5d
7 changed files with 100 additions and 49 deletions

1
Cargo.lock generated
View file

@ -2733,6 +2733,7 @@ dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -25,6 +25,7 @@ testing = []
app_units = "0.4" app_units = "0.4"
atomic_refcell = "0.1" atomic_refcell = "0.1"
bitflags = "0.7" bitflags = "0.7"
bit-vec = "0.4.3"
byteorder = "1.0" byteorder = "1.0"
cfg-if = "0.1.0" cfg-if = "0.1.0"
cssparser = "0.12.1" cssparser = "0.12.1"

View file

@ -7,6 +7,7 @@
use animation::{Animation, PropertyAnimation}; use animation::{Animation, PropertyAnimation};
use app_units::Au; use app_units::Au;
use bit_vec::BitVec;
use bloom::StyleBloom; use bloom::StyleBloom;
use data::ElementData; use data::ElementData;
use dom::{OpaqueNode, TNode, TElement, SendElement}; use dom::{OpaqueNode, TNode, TElement, SendElement};
@ -111,6 +112,9 @@ pub struct CurrentElementInfo {
element: OpaqueNode, element: OpaqueNode,
/// Whether the element is being styled for the first time. /// Whether the element is being styled for the first time.
is_initial_style: bool, is_initial_style: bool,
/// Lazy cache of the result of matching the current element against the
/// revalidation selectors.
pub revalidation_match_results: Option<BitVec>,
/// A Vec of possibly expired animations. Used only by Servo. /// A Vec of possibly expired animations. Used only by Servo.
#[allow(dead_code)] #[allow(dead_code)]
pub possibly_expired_animations: Vec<PropertyAnimation>, pub possibly_expired_animations: Vec<PropertyAnimation>,
@ -317,6 +321,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
self.current_element_info = Some(CurrentElementInfo { self.current_element_info = Some(CurrentElementInfo {
element: element.as_node().opaque(), element: element.as_node().opaque(),
is_initial_style: !data.has_styles(), is_initial_style: !data.has_styles(),
revalidation_match_results: None,
possibly_expired_animations: Vec::new(), possibly_expired_animations: Vec::new(),
}); });
} }

View file

@ -39,6 +39,7 @@
extern crate app_units; extern crate app_units;
extern crate atomic_refcell; extern crate atomic_refcell;
extern crate bit_vec;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[allow(unused_extern_crates)] extern crate byteorder; #[allow(unused_extern_crates)] extern crate byteorder;

View file

@ -10,9 +10,10 @@
use Atom; use Atom;
use animation::{self, Animation, PropertyAnimation}; use animation::{self, Animation, PropertyAnimation};
use atomic_refcell::AtomicRefMut; use atomic_refcell::AtomicRefMut;
use bit_vec::BitVec;
use cache::{LRUCache, LRUCacheMutIterator}; use cache::{LRUCache, LRUCacheMutIterator};
use cascade_info::CascadeInfo; use cascade_info::CascadeInfo;
use context::{SequentialTask, SharedStyleContext, StyleContext}; use context::{CurrentElementInfo, SequentialTask, SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles, RestyleData}; use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
use dom::{AnimationRules, SendElement, TElement, TNode}; use dom::{AnimationRules, SendElement, TElement, TNode};
use font_metrics::FontMetricsProvider; use font_metrics::FontMetricsProvider;
@ -53,6 +54,8 @@ struct StyleSharingCandidate<E: TElement> {
element: SendElement<E>, element: SendElement<E>,
/// The cached class names. /// The cached class names.
class_attributes: Option<Vec<Atom>>, class_attributes: Option<Vec<Atom>>,
/// The cached result of matching this entry against the revalidation selectors.
revalidation_match_results: Option<BitVec>,
} }
impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> { impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> {
@ -103,7 +106,9 @@ pub enum CacheMiss {
fn element_matches_candidate<E: TElement>(element: &E, fn element_matches_candidate<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate<E>, candidate: &mut StyleSharingCandidate<E>,
candidate_element: &E, candidate_element: &E,
shared_context: &SharedStyleContext) shared: &SharedStyleContext,
bloom: &BloomFilter,
info: &mut CurrentElementInfo)
-> Result<ComputedStyle, CacheMiss> { -> Result<ComputedStyle, CacheMiss> {
macro_rules! miss { macro_rules! miss {
($miss: ident) => { ($miss: ident) => {
@ -151,7 +156,7 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(PresHints) miss!(PresHints)
} }
if !revalidate(element, candidate_element, shared_context) { if !revalidate(element, candidate, candidate_element, shared, bloom, info) {
miss!(Revalidation) miss!(Revalidation)
} }
@ -194,9 +199,37 @@ fn have_same_class<E: TElement>(element: &E,
#[inline] #[inline]
fn revalidate<E: TElement>(element: &E, fn revalidate<E: TElement>(element: &E,
candidate: &E, candidate: &mut StyleSharingCandidate<E>,
ctx: &SharedStyleContext) -> bool { candidate_element: &E,
ctx.stylist.revalidate_entry_for_cache(element, candidate) shared: &SharedStyleContext,
bloom: &BloomFilter,
info: &mut CurrentElementInfo)
-> bool {
// NB: We could avoid matching ancestor selectors entirely (rather than
// just depending on the bloom filter), at the expense of some complexity.
// Gecko bug 1354965 tracks this.
//
// We could also be even more careful about only matching the minimal number
// of revalidation selectors until we find a mismatch. Gecko bug 1355668
// tracks this.
//
// These potential optimizations may not be worth the complexity.
let stylist = &shared.stylist;
if info.revalidation_match_results.is_none() {
info.revalidation_match_results =
Some(stylist.match_revalidation_selectors(element, bloom));
}
if candidate.revalidation_match_results.is_none() {
candidate.revalidation_match_results =
Some(stylist.match_revalidation_selectors(candidate_element, bloom));
}
let for_element = info.revalidation_match_results.as_ref().unwrap();
let for_candidate = candidate.revalidation_match_results.as_ref().unwrap();
debug_assert!(for_element.len() == for_candidate.len());
for_element == for_candidate
} }
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8; static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8;
@ -219,7 +252,8 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
pub fn insert_if_possible(&mut self, pub fn insert_if_possible(&mut self,
element: &E, element: &E,
style: &Arc<ComputedValues>, style: &Arc<ComputedValues>,
relations: StyleRelations) { relations: StyleRelations,
revalidation_match_results: Option<BitVec>) {
let parent = match element.parent_element() { let parent = match element.parent_element() {
Some(element) => element, Some(element) => element,
None => { None => {
@ -252,6 +286,7 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
self.cache.insert(StyleSharingCandidate { self.cache.insert(StyleSharingCandidate {
element: unsafe { SendElement::new(*element) }, element: unsafe { SendElement::new(*element) },
class_attributes: None, class_attributes: None,
revalidation_match_results: revalidation_match_results,
}); });
} }
@ -632,11 +667,14 @@ trait PrivateMatchMethods: TElement {
} }
fn share_style_with_candidate_if_possible(&self, fn share_style_with_candidate_if_possible(&self,
shared_context: &SharedStyleContext, candidate: &mut StyleSharingCandidate<Self>,
candidate: &mut StyleSharingCandidate<Self>) shared: &SharedStyleContext,
bloom: &BloomFilter,
info: &mut CurrentElementInfo)
-> Result<ComputedStyle, CacheMiss> { -> Result<ComputedStyle, CacheMiss> {
let candidate_element = *candidate.element; let candidate_element = *candidate.element;
element_matches_candidate(self, candidate, &candidate_element, shared_context) element_matches_candidate(self, candidate, &candidate_element,
shared, bloom, info)
} }
} }
@ -699,11 +737,23 @@ pub trait MatchMethods : TElement {
// If the style is shareable, add it to the LRU cache. // If the style is shareable, add it to the LRU cache.
if sharing == StyleSharingBehavior::Allow && relations_are_shareable(&primary_relations) { if sharing == StyleSharingBehavior::Allow && relations_are_shareable(&primary_relations) {
// If we previously tried to match this element against the cache,
// the revalidation match results will already be cached. Otherwise
// we'll have None, and compute them later on-demand.
//
// If we do have the results, grab them here to satisfy the borrow
// checker.
let revalidation_match_results = context.thread_local
.current_element_info
.as_mut().unwrap()
.revalidation_match_results
.take();
context.thread_local context.thread_local
.style_sharing_candidate_cache .style_sharing_candidate_cache
.insert_if_possible(self, .insert_if_possible(self,
data.styles().primary.values(), data.styles().primary.values(),
primary_relations); primary_relations,
revalidation_match_results);
} }
} }
@ -951,9 +1001,7 @@ pub trait MatchMethods : TElement {
/// live nodes in it, and we have no way to guarantee that at the type /// live nodes in it, and we have no way to guarantee that at the type
/// system level yet. /// system level yet.
unsafe fn share_style_if_possible(&self, unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache: context: &mut StyleContext<Self>,
&mut StyleSharingCandidateCache<Self>,
shared_context: &SharedStyleContext,
data: &mut AtomicRefMut<ElementData>) data: &mut AtomicRefMut<ElementData>)
-> StyleSharingResult { -> StyleSharingResult {
if is_share_style_cache_disabled() { if is_share_style_cache_disabled() {
@ -972,11 +1020,17 @@ pub trait MatchMethods : TElement {
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
let cache = &mut context.thread_local.style_sharing_candidate_cache;
let current_element_info =
&mut context.thread_local.current_element_info.as_mut().unwrap();
let bloom = context.thread_local.bloom_filter.filter();
let mut should_clear_cache = false; let mut should_clear_cache = false;
for (i, candidate) in style_sharing_candidate_cache.iter_mut().enumerate() { for (i, candidate) in cache.iter_mut().enumerate() {
let sharing_result = let sharing_result =
self.share_style_with_candidate_if_possible(shared_context, self.share_style_with_candidate_if_possible(candidate,
candidate); &context.shared,
bloom,
current_element_info);
match sharing_result { match sharing_result {
Ok(shared_style) => { Ok(shared_style) => {
// Yay, cache hit. Share the style. // Yay, cache hit. Share the style.
@ -986,7 +1040,8 @@ pub trait MatchMethods : TElement {
let old_values = data.get_styles_mut() let old_values = data.get_styles_mut()
.and_then(|s| s.primary.values.take()); .and_then(|s| s.primary.values.take());
if let Some(old) = old_values { if let Some(old) = old_values {
self.accumulate_damage(shared_context, data.restyle_mut(), &old, self.accumulate_damage(&context.shared,
data.restyle_mut(), &old,
shared_style.values(), None); shared_style.values(), None);
} }
@ -1021,7 +1076,7 @@ pub trait MatchMethods : TElement {
} }
} }
if should_clear_cache { if should_clear_cache {
style_sharing_candidate_cache.clear(); cache.clear();
} }
StyleSharingResult::CannotShare StyleSharingResult::CannotShare

View file

@ -7,6 +7,7 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use {Atom, LocalName}; use {Atom, LocalName};
use bit_vec::BitVec;
use data::ComputedStyle; use data::ComputedStyle;
use dom::{AnimationRules, PresentationalHintsSynthetizer, TElement}; use dom::{AnimationRules, PresentationalHintsSynthetizer, TElement};
use error_reporting::StdoutErrorReporter; use error_reporting::StdoutErrorReporter;
@ -765,41 +766,30 @@ impl Stylist {
self.rule_tree.root() self.rule_tree.root()
} }
/// Returns whether two elements match the same set of revalidation- /// Computes the match results of a given element against the set of
/// requiring rules. /// revalidation selectors.
/// pub fn match_revalidation_selectors<E>(&self,
/// This is also for the style sharing candidate cache. element: &E,
pub fn revalidate_entry_for_cache<E>(&self, bloom: &BloomFilter)
element: &E, -> BitVec
candidate: &E) -> bool
where E: TElement, where E: TElement,
{ {
use selectors::matching::StyleRelations; use selectors::matching::StyleRelations;
use selectors::matching::matches_complex_selector; use selectors::matching::matches_complex_selector;
// TODO(emilio): we can probably do better, the candidate should already
// know what rules it matches.
//
// TODO(emilio): Use the bloom filter, since they contain the element's
// ancestor chain and it's correct for the candidate too.
for ref selector in self.selectors_for_cache_revalidation.iter() {
let element_matches =
matches_complex_selector(&selector.complex_selector, element,
None, &mut StyleRelations::empty(),
&mut |_, _| {});
let candidate_matches = let len = self.selectors_for_cache_revalidation.len();
matches_complex_selector(&selector.complex_selector, candidate, let mut results = BitVec::from_elem(len, false);
None, &mut StyleRelations::empty(),
&mut |_, _| {});
if element_matches != candidate_matches { for (i, ref selector) in self.selectors_for_cache_revalidation
debug!("match_same_sibling_affecting_rules: Failure due to {:?}", .iter().enumerate() {
selector.complex_selector); results.set(i, matches_complex_selector(&selector.complex_selector,
return false; element,
} Some(bloom),
&mut StyleRelations::empty(),
&mut |_, _| {}));
} }
true results
} }
/// Given an element, and a snapshot that represents a previous state of the /// Given an element, and a snapshot that represents a previous state of the

View file

@ -680,15 +680,13 @@ fn compute_style<E, D>(_traversal: &D,
use matching::StyleSharingResult::*; use matching::StyleSharingResult::*;
context.thread_local.statistics.elements_styled += 1; context.thread_local.statistics.elements_styled += 1;
let shared_context = context.shared;
let kind = data.restyle_kind(); let kind = data.restyle_kind();
// First, try the style sharing cache. If we get a match we can skip the rest // First, try the style sharing cache. If we get a match we can skip the rest
// of the work. // of the work.
if let MatchAndCascade = kind { if let MatchAndCascade = kind {
let sharing_result = unsafe { let sharing_result = unsafe {
let cache = &mut context.thread_local.style_sharing_candidate_cache; element.share_style_if_possible(context, &mut data)
element.share_style_if_possible(cache, shared_context, &mut data)
}; };
if let StyleWasShared(index) = sharing_result { if let StyleWasShared(index) = sharing_result {
context.thread_local.statistics.styles_shared += 1; context.thread_local.statistics.styles_shared += 1;