mirror of
https://github.com/servo/servo.git
synced 2025-07-23 23:33:43 +01:00
Do a second pass on the sharing cache to reuse style by rule node identity.
MozReview-Commit-ID: H67j3Sbt3gr
This commit is contained in:
parent
7a7070e075
commit
1c9b39a8e8
6 changed files with 122 additions and 8 deletions
|
@ -313,6 +313,9 @@ pub struct TraversalStatistics {
|
|||
pub elements_matched: u32,
|
||||
/// The number of cache hits from the StyleSharingCache.
|
||||
pub styles_shared: u32,
|
||||
/// The number of styles reused via rule node comparison from the
|
||||
/// StyleSharingCache.
|
||||
pub styles_reused: u32,
|
||||
/// The number of selectors in the stylist.
|
||||
pub selectors: u32,
|
||||
/// The number of revalidation selectors.
|
||||
|
@ -347,6 +350,7 @@ impl<'a> ops::Add for &'a TraversalStatistics {
|
|||
elements_styled: self.elements_styled + other.elements_styled,
|
||||
elements_matched: self.elements_matched + other.elements_matched,
|
||||
styles_shared: self.styles_shared + other.styles_shared,
|
||||
styles_reused: self.styles_reused + other.styles_reused,
|
||||
selectors: 0,
|
||||
revalidation_selectors: 0,
|
||||
dependency_selectors: 0,
|
||||
|
@ -374,6 +378,7 @@ impl fmt::Display for TraversalStatistics {
|
|||
writeln!(f, "[PERF],elements_styled,{}", self.elements_styled)?;
|
||||
writeln!(f, "[PERF],elements_matched,{}", self.elements_matched)?;
|
||||
writeln!(f, "[PERF],styles_shared,{}", self.styles_shared)?;
|
||||
writeln!(f, "[PERF],styles_reused,{}", self.styles_reused)?;
|
||||
writeln!(f, "[PERF],selectors,{}", self.selectors)?;
|
||||
writeln!(f, "[PERF],revalidation_selectors,{}", self.revalidation_selectors)?;
|
||||
writeln!(f, "[PERF],dependency_selectors,{}", self.dependency_selectors)?;
|
||||
|
|
|
@ -22,8 +22,9 @@ use std::ops::{Deref, DerefMut};
|
|||
use style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
|
||||
|
||||
bitflags! {
|
||||
/// Various flags stored on ElementData.
|
||||
#[derive(Default)]
|
||||
flags ElementDataFlags: u8 {
|
||||
pub flags ElementDataFlags: u8 {
|
||||
/// Whether the styles changed for this restyle.
|
||||
const WAS_RESTYLED = 1 << 0,
|
||||
/// Whether the last traversal of this element did not do
|
||||
|
@ -37,6 +38,13 @@ bitflags! {
|
|||
const TRAVERSED_WITHOUT_STYLING = 1 << 1,
|
||||
/// Whether we reframed/reconstructed any ancestor or self.
|
||||
const ANCESTOR_WAS_RECONSTRUCTED = 1 << 2,
|
||||
/// Whether the primary style of this element data was reused from another
|
||||
/// element via a rule node comparison. This allows us to differentiate
|
||||
/// between elements that shared styles because they met all the criteria
|
||||
/// of the style sharing cache, compared to elements that reused style
|
||||
/// structs via rule node identity. The former gives us stronger transitive
|
||||
/// guarantees that allows us to apply the style sharing cache to cousins.
|
||||
const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +210,7 @@ pub struct ElementData {
|
|||
pub hint: RestyleHint,
|
||||
|
||||
/// Flags.
|
||||
flags: ElementDataFlags,
|
||||
pub flags: ElementDataFlags,
|
||||
}
|
||||
|
||||
/// The kind of restyle that a single element should do.
|
||||
|
@ -276,11 +284,17 @@ impl ElementData {
|
|||
|
||||
/// Returns this element's primary style as a resolved style to use for sharing.
|
||||
pub fn share_primary_style(&self) -> PrimaryStyle {
|
||||
PrimaryStyle(ResolvedStyle::new(self.styles.primary().clone()))
|
||||
let primary_is_reused = self.flags.contains(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
|
||||
PrimaryStyle(ResolvedStyle::new(self.styles.primary().clone(), primary_is_reused))
|
||||
}
|
||||
|
||||
/// Sets a new set of styles, returning the old ones.
|
||||
pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
|
||||
if new_styles.primary.0.reused_via_rule_node {
|
||||
self.flags.insert(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
|
||||
} else {
|
||||
self.flags.remove(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
|
||||
}
|
||||
mem::replace(&mut self.styles, new_styles.into())
|
||||
}
|
||||
|
||||
|
@ -442,6 +456,28 @@ impl ElementData {
|
|||
#[cfg(feature = "servo")]
|
||||
pub fn skip_applying_damage(&self) -> bool { false }
|
||||
|
||||
/// Returns whether it is safe to perform cousin sharing based on the ComputedValues
|
||||
/// identity of the primary style in this ElementData. There are a few subtle things
|
||||
/// to check.
|
||||
///
|
||||
/// First, if a parent element was already styled and we traversed past it without
|
||||
/// restyling it, that may be because our clever invalidation logic was able to prove
|
||||
/// that the styles of that element would remain unchanged despite changes to the id
|
||||
/// or class attributes. However, style sharing relies on the strong guarantee that all
|
||||
/// the classes and ids up the respective parent chains are identical. As such, if we
|
||||
/// skipped styling for one (or both) of the parents on this traversal, we can't share
|
||||
/// styles across cousins. Note that this is a somewhat conservative check. We could
|
||||
/// tighten it by having the invalidation logic explicitly flag elements for which it
|
||||
/// ellided styling.
|
||||
///
|
||||
/// Second, we want to only consider elements whose ComputedValues match due to a hit
|
||||
/// in the style sharing cache, rather than due to the rule-node-based reuse that
|
||||
/// happens later in the styling pipeline. The former gives us the stronger guarantees
|
||||
/// we need for style sharing, the latter does not.
|
||||
pub fn safe_for_cousin_sharing(&self) -> bool {
|
||||
!self.flags.intersects(TRAVERSED_WITHOUT_STYLING | PRIMARY_STYLE_REUSED_VIA_RULE_NODE)
|
||||
}
|
||||
|
||||
/// Measures memory usage.
|
||||
#[cfg(feature = "gecko")]
|
||||
pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||
|
|
|
@ -1997,6 +1997,13 @@ pub struct ComputedValues {
|
|||
inner: ComputedValuesInner,
|
||||
}
|
||||
|
||||
impl ComputedValues {
|
||||
/// Returns the visited rules, if applicable.
|
||||
pub fn visited_rules(&self) -> Option<<&StrongRuleNode> {
|
||||
self.visited_style.as_ref().and_then(|s| s.rules.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "servo")]
|
||||
impl ComputedValues {
|
||||
/// Create a new refcounted `ComputedValues`
|
||||
|
|
|
@ -46,8 +46,7 @@ pub fn parents_allow_sharing<E>(
|
|||
// invalidation logic explicitly flag elements for which it ellided styling.
|
||||
let parent_data = parent.borrow_data().unwrap();
|
||||
let candidate_parent_data = candidate_parent.borrow_data().unwrap();
|
||||
if parent_data.traversed_without_styling() ||
|
||||
candidate_parent_data.traversed_without_styling() {
|
||||
if !parent_data.safe_for_cousin_sharing() || !candidate_parent_data.safe_for_cousin_sharing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ use dom::{TElement, SendElement};
|
|||
use matching::MatchMethods;
|
||||
use owning_ref::OwningHandle;
|
||||
use properties::ComputedValues;
|
||||
use rule_tree::StrongRuleNode;
|
||||
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
|
||||
use servo_arc::{Arc, NonZeroPtrMut};
|
||||
use smallbitvec::SmallBitVec;
|
||||
|
@ -115,6 +116,8 @@ impl OpaqueComputedValues {
|
|||
let p = NonZeroPtrMut::new(cv as *const ComputedValues as *const () as *mut ());
|
||||
OpaqueComputedValues(p)
|
||||
}
|
||||
|
||||
fn eq(&self, cv: &ComputedValues) -> bool { Self::from(cv) == *self }
|
||||
}
|
||||
|
||||
/// Some data we want to avoid recomputing all the time while trying to share
|
||||
|
@ -730,4 +733,44 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
|
||||
true
|
||||
}
|
||||
|
||||
/// Attempts to find an element in the cache with the given primary rule node and parent.
|
||||
pub fn lookup_by_rules(
|
||||
&mut self,
|
||||
inherited: &ComputedValues,
|
||||
rules: &StrongRuleNode,
|
||||
visited_rules: Option<&StrongRuleNode>,
|
||||
target: E,
|
||||
) -> Option<E> {
|
||||
self.cache_mut().lookup(|candidate| {
|
||||
if !candidate.parent_style_identity().eq(inherited) {
|
||||
return false;
|
||||
}
|
||||
let data = candidate.element.borrow_data().unwrap();
|
||||
let style = data.styles.primary();
|
||||
if style.rules.as_ref() != Some(&rules) {
|
||||
return false;
|
||||
}
|
||||
if style.visited_rules() != visited_rules {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rule nodes and styles are computed independent of the element's
|
||||
// actual visitedness, but at the end of the cascade (in
|
||||
// `adjust_for_visited`) we do store the visitedness as a flag in
|
||||
// style. (This is a subtle change from initial visited work that
|
||||
// landed when computed values were fused, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1381635.)
|
||||
// So at the moment, we need to additionally compare visitedness,
|
||||
// since that is not accounted for by rule nodes alone.
|
||||
// FIXME(jryans): This seems like it breaks the constant time
|
||||
// requirements of visited, assuming we get a cache hit on only one
|
||||
// of unvisited vs. visited.
|
||||
if target.is_visited_link() != candidate.element.is_visited_link() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ struct MatchingResults {
|
|||
pub struct ResolvedStyle {
|
||||
/// The style itself.
|
||||
pub style: Arc<ComputedValues>,
|
||||
/// Whether the style was reused from another element via the rule node.
|
||||
pub reused_via_rule_node: bool,
|
||||
}
|
||||
|
||||
/// The primary style of an element or an element-backed pseudo-element.
|
||||
|
@ -68,8 +70,8 @@ pub struct ResolvedElementStyles {
|
|||
|
||||
impl ResolvedStyle {
|
||||
/// Creates a new ResolvedStyle.
|
||||
pub fn new(style: Arc<ComputedValues>) -> Self {
|
||||
ResolvedStyle { style }
|
||||
pub fn new(style: Arc<ComputedValues>, reused_via_rule_node: bool) -> Self {
|
||||
ResolvedStyle { style, reused_via_rule_node }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,6 +266,27 @@ where
|
|||
layout_parent_style: Option<&ComputedValues>,
|
||||
pseudo: Option<&PseudoElement>,
|
||||
) -> ResolvedStyle {
|
||||
// Before doing the cascade, check the sharing cache and see if we can reuse
|
||||
// the style via rule node identity.
|
||||
let may_reuse = pseudo.is_none() && !self.element.is_native_anonymous() &&
|
||||
parent_style.is_some() && inputs.rules.is_some();
|
||||
if may_reuse {
|
||||
let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
|
||||
parent_style.unwrap(),
|
||||
inputs.rules.as_ref().unwrap(),
|
||||
inputs.visited_rules.as_ref(),
|
||||
self.element,
|
||||
);
|
||||
if let Some(el) = cached {
|
||||
self.context.thread_local.statistics.styles_reused += 1;
|
||||
let mut style = el.borrow_data().unwrap().share_primary_style().0;
|
||||
style.reused_via_rule_node = true;
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
// No style to reuse. Cascade the style, starting with visited style
|
||||
// if necessary.
|
||||
let mut style_if_visited = None;
|
||||
if parent_style.map_or(false, |s| s.get_visited_style().is_some()) ||
|
||||
inputs.visited_rules.is_some() {
|
||||
|
@ -285,7 +308,8 @@ where
|
|||
layout_parent_style,
|
||||
CascadeVisitedMode::Unvisited,
|
||||
pseudo,
|
||||
)
|
||||
),
|
||||
reused_via_rule_node: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue