diff --git a/components/style/context.rs b/components/style/context.rs index 7c9f0f0ab89..49141c7cf2e 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -16,6 +16,9 @@ use matching::StyleSharingCandidateCache; use parking_lot::RwLock; use properties::ComputedValues; use std::collections::HashMap; +use std::env; +use std::fmt; +use std::ops::Add; use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use stylist::Stylist; @@ -102,6 +105,65 @@ struct CurrentElementInfo { is_initial_style: bool, } +/// Statistics gathered during the traversal. We gather statistics on each thread +/// and then combine them after the threads join via the Add implementation below. +#[derive(Default)] +pub struct TraversalStatistics { + /// The total number of elements traversed. + pub elements_traversed: u32, + /// The number of elements where has_styles() went from false to true. + pub elements_styled: u32, + /// The number of elements for which we performed selector matching. + pub elements_matched: u32, + /// The number of cache hits from the StyleSharingCache. + pub styles_shared: u32, +} + +/// Implementation of Add to aggregate statistics across different threads. +impl<'a> Add for &'a TraversalStatistics { + type Output = TraversalStatistics; + fn add(self, other: Self) -> TraversalStatistics { + TraversalStatistics { + elements_traversed: self.elements_traversed + other.elements_traversed, + elements_styled: self.elements_styled + other.elements_styled, + elements_matched: self.elements_matched + other.elements_matched, + styles_shared: self.styles_shared + other.styles_shared, + } + } +} + +/// Format the statistics in a way that the performance test harness understands. +/// See https://bugzilla.mozilla.org/show_bug.cgi?id=1331856#c2 +impl fmt::Display for TraversalStatistics { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "[PERF] perf block start")); + try!(writeln!(f, "[PERF],elements_traversed,{}", self.elements_traversed)); + try!(writeln!(f, "[PERF],elements_styled,{}", self.elements_styled)); + try!(writeln!(f, "[PERF],elements_matched,{}", self.elements_matched)); + try!(writeln!(f, "[PERF],styles_shared,{}", self.styles_shared)); + writeln!(f, "[PERF] perf block end") + } +} + +lazy_static! { + /// Whether to dump style statistics, computed statically. We use an environmental + /// variable so that this is easy to set for Gecko builds, and matches the + /// mechanism we use to dump statistics on the Gecko style system. + static ref DUMP_STYLE_STATISTICS: bool = { + match env::var("DUMP_STYLE_STATISTICS") { + Ok(s) => !s.is_empty(), + Err(_) => false, + } + }; +} + +impl TraversalStatistics { + /// Returns whether statistics dumping is enabled. + pub fn should_dump() -> bool { + *DUMP_STYLE_STATISTICS + } +} + /// A thread-local style context. /// /// This context contains data that needs to be used during restyling, but is @@ -115,6 +177,8 @@ pub struct ThreadLocalStyleContext { /// A channel on which new animations that have been triggered by style /// recalculation can be sent. pub new_animations_sender: Sender, + /// Statistics about the traversal. + pub statistics: TraversalStatistics, /// Information related to the current element, non-None during processing. current_element_info: Option, } @@ -126,6 +190,7 @@ impl ThreadLocalStyleContext { style_sharing_candidate_cache: StyleSharingCandidateCache::new(), bloom_filter: StyleBloom::new(), new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(), + statistics: TraversalStatistics::default(), current_element_info: None, } } diff --git a/components/style/parallel.rs b/components/style/parallel.rs index 70250073d24..89e1331bfc6 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -22,13 +22,13 @@ #![deny(missing_docs)] +use context::TraversalStatistics; use dom::{OpaqueNode, SendNode, TElement, TNode}; use rayon; use scoped_tls::ScopedTLS; use servo_config::opts; -use std::sync::atomic::Ordering; +use std::borrow::Borrow; use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; -use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES}; /// The chunk size used to split the parallel traversal nodes. /// @@ -45,11 +45,6 @@ pub fn traverse_dom(traversal: &D, where E: TElement, D: DomTraversal, { - if opts::get().style_sharing_stats { - STYLE_SHARING_CACHE_HITS.store(0, Ordering::SeqCst); - STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst); - } - // Handle Gecko's eager initial styling. We don't currently support it // in conjunction with bottom-up traversal. If we did, we'd need to put // it on the context to make it available to the bottom-up phase. @@ -78,13 +73,16 @@ pub fn traverse_dom(traversal: &D, }); }); - if opts::get().style_sharing_stats { - let hits = STYLE_SHARING_CACHE_HITS.load(Ordering::SeqCst); - let misses = STYLE_SHARING_CACHE_MISSES.load(Ordering::SeqCst); - - println!("Style sharing stats:"); - println!(" * Hits: {}", hits); - println!(" * Misses: {}", misses); + // Dump statistics to stdout if requested. + if TraversalStatistics::should_dump() || opts::get().style_sharing_stats { + let slots = unsafe { tls.unsafe_get() }; + let aggregate = slots.iter().fold(TraversalStatistics::default(), |acc, t| { + match *t.borrow() { + None => acc, + Some(ref cx) => &cx.borrow().statistics + &acc, + } + }); + println!("{}", aggregate); } } diff --git a/components/style/scoped_tls.rs b/components/style/scoped_tls.rs index 92cfd2a4659..d3e63970921 100644 --- a/components/style/scoped_tls.rs +++ b/components/style/scoped_tls.rs @@ -61,4 +61,10 @@ impl<'scope, T: Send> ScopedTLS<'scope, T> { RefMut::map(opt, |x| x.as_mut().unwrap()) } + + /// Unsafe access to the slots. This can be used to access the TLS when + /// the caller knows that the pool does not have access to the TLS. + pub unsafe fn unsafe_get(&self) -> &[RefCell>] { + &self.slots + } } diff --git a/components/style/sequential.rs b/components/style/sequential.rs index cc2a43bba27..cbb5e253dee 100644 --- a/components/style/sequential.rs +++ b/components/style/sequential.rs @@ -6,7 +6,9 @@ #![deny(missing_docs)] +use context::TraversalStatistics; use dom::{TElement, TNode}; +use std::borrow::Borrow; use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; /// Do a sequential DOM traversal for layout or styling, generic over `D`. @@ -57,4 +59,10 @@ pub fn traverse_dom(traversal: &D, } else { doit(traversal, &mut traversal_data, &mut tlc, root.as_node()); } + + // Dump statistics to stdout if requested. + let tlsc = tlc.borrow(); + if TraversalStatistics::should_dump() { + println!("{}", tlsc.statistics); + } } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 694a4aff53f..9e425043916 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -16,16 +16,8 @@ use selector_parser::RestyleDamage; use servo_config::opts; use std::borrow::BorrowMut; use std::mem; -use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; use stylist::Stylist; -/// Style sharing candidate cache hits. These are only used when -/// `-Z style-sharing-stats` is given. -pub static STYLE_SHARING_CACHE_HITS: AtomicUsize = ATOMIC_USIZE_INIT; - -/// Style sharing candidate cache misses. -pub static STYLE_SHARING_CACHE_MISSES: AtomicUsize = ATOMIC_USIZE_INIT; - /// A per-traversal-level chunk of data. This is sent down by the traversal, and /// currently only holds the dom depth for the bloom filter. /// @@ -386,6 +378,7 @@ pub fn recalc_style_at(traversal: &D, D: DomTraversal { context.thread_local.begin_element(element, &data); + context.thread_local.statistics.elements_traversed += 1; debug_assert!(data.get_restyle().map_or(true, |r| r.snapshot.is_none()), "Snapshots should be expanded by the caller"); @@ -446,6 +439,7 @@ fn compute_style(_traversal: &D, where E: TElement, D: DomTraversal, { + context.thread_local.statistics.elements_styled += 1; let shared_context = context.shared; // Ensure the bloom filter is up to date. let dom_depth = context.thread_local.bloom_filter @@ -472,11 +466,8 @@ fn compute_style(_traversal: &D, StyleSharingResult::CannotShare => { let match_results; let shareable_element = { - if opts::get().style_sharing_stats { - STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed); - } - // Perform the CSS selector matching. + context.thread_local.statistics.elements_matched += 1; let filter = context.thread_local.bloom_filter.filter(); match_results = element.match_element(context, Some(filter)); if match_results.primary_is_shareable() { @@ -505,9 +496,7 @@ fn compute_style(_traversal: &D, } } StyleSharingResult::StyleWasShared(index) => { - if opts::get().style_sharing_stats { - STYLE_SHARING_CACHE_HITS.fetch_add(1, Ordering::Relaxed); - } + context.thread_local.statistics.styles_shared += 1; context.thread_local.style_sharing_candidate_cache.touch(index); } }