Switch to an iterator for traverse_nodes.

We can't use a slice anymore, because the input might be the VecDeque from the
sequential traversal, which requires an iterator to handle the ring buffer.

MozReview-Commit-ID: JwH6MtRgMfY
This commit is contained in:
Bobby Holley 2017-08-24 12:53:39 -07:00
parent 2908b52c7b
commit f7c6b2f04e

View file

@ -25,6 +25,7 @@
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use context::{StyleContext, ThreadLocalStyleContext, TraversalStatistics}; use context::{StyleContext, ThreadLocalStyleContext, TraversalStatistics};
use dom::{OpaqueNode, SendNode, TElement, TNode}; use dom::{OpaqueNode, SendNode, TElement, TNode};
use itertools::Itertools;
use rayon; use rayon;
use scoped_tls::ScopedTLS; use scoped_tls::ScopedTLS;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -78,7 +79,7 @@ pub fn traverse_dom<E, D>(traversal: &D,
rayon::scope(|scope| { rayon::scope(|scope| {
let root = send_root; let root = send_root;
let root_opaque = root.opaque(); let root_opaque = root.opaque();
traverse_nodes(&[root], traverse_nodes(Some(root).into_iter(),
DispatchMode::TailCall, DispatchMode::TailCall,
true, true,
root_opaque, root_opaque,
@ -210,7 +211,7 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
if discovered_child_nodes.len() >= WORK_UNIT_MAX { if discovered_child_nodes.len() >= WORK_UNIT_MAX {
let mut traversal_data_copy = traversal_data.clone(); let mut traversal_data_copy = traversal_data.clone();
traversal_data_copy.current_dom_depth += 1; traversal_data_copy.current_dom_depth += 1;
traverse_nodes(&*discovered_child_nodes, traverse_nodes(discovered_child_nodes.drain(),
DispatchMode::NotTailCall, DispatchMode::NotTailCall,
recursion_ok, recursion_ok,
root, root,
@ -219,7 +220,6 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
pool, pool,
traversal, traversal,
tls); tls);
discovered_child_nodes.clear();
} }
let node = **n; let node = **n;
@ -240,7 +240,7 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
// worth of them) directly on this thread by passing TailCall. // worth of them) directly on this thread by passing TailCall.
if !discovered_child_nodes.is_empty() { if !discovered_child_nodes.is_empty() {
traversal_data.current_dom_depth += 1; traversal_data.current_dom_depth += 1;
traverse_nodes(&discovered_child_nodes, traverse_nodes(discovered_child_nodes.drain(),
DispatchMode::TailCall, DispatchMode::TailCall,
recursion_ok, recursion_ok,
root, root,
@ -264,8 +264,11 @@ impl DispatchMode {
fn is_tail_call(&self) -> bool { matches!(*self, DispatchMode::TailCall) } fn is_tail_call(&self) -> bool { matches!(*self, DispatchMode::TailCall) }
} }
/// Enqueues |nodes| for processing, possibly on this thread if the tail call
/// conditions are met.
#[inline] #[inline]
fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode<E::ConcreteNode>], fn traverse_nodes<'a, 'scope, E, D, I>(
nodes: I,
mode: DispatchMode, mode: DispatchMode,
recursion_ok: bool, recursion_ok: bool,
root: OpaqueNode, root: OpaqueNode,
@ -273,11 +276,14 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode<E::ConcreteNode>],
scope: &'a rayon::Scope<'scope>, scope: &'a rayon::Scope<'scope>,
pool: &'scope rayon::ThreadPool, pool: &'scope rayon::ThreadPool,
traversal: &'scope D, traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>) tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>
where E: TElement + 'scope, )
where
E: TElement + 'scope,
D: DomTraversal<E>, D: DomTraversal<E>,
I: ExactSizeIterator<Item = SendNode<E::ConcreteNode>>
{ {
debug_assert!(!nodes.is_empty()); debug_assert_ne!(nodes.len(), 0);
// This is a tail call from the perspective of the caller. However, we only // This is a tail call from the perspective of the caller. However, we only
// want to actually dispatch the job as a tail call if there's nothing left // want to actually dispatch the job as a tail call if there's nothing left
@ -293,7 +299,7 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode<E::ConcreteNode>],
// In the common case, our children fit within a single work unit, in which // In the common case, our children fit within a single work unit, in which
// case we can pass the SmallVec directly and avoid extra allocation. // case we can pass the SmallVec directly and avoid extra allocation.
if nodes.len() <= WORK_UNIT_MAX { if nodes.len() <= WORK_UNIT_MAX {
let work = nodes.iter().cloned().collect::<WorkUnit<E::ConcreteNode>>(); let work: WorkUnit<E::ConcreteNode> = nodes.collect();
if may_dispatch_tail { if may_dispatch_tail {
top_down_dom(&work, root, top_down_dom(&work, root,
traversal_data, scope, pool, traversal, tls); traversal_data, scope, pool, traversal, tls);
@ -305,8 +311,8 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode<E::ConcreteNode>],
}); });
} }
} else { } else {
for chunk in nodes.chunks(WORK_UNIT_MAX) { for chunk in nodes.chunks(WORK_UNIT_MAX).into_iter() {
let nodes = chunk.iter().cloned().collect::<WorkUnit<E::ConcreteNode>>(); let nodes: WorkUnit<E::ConcreteNode> = chunk.collect();
let traversal_data_copy = traversal_data.clone(); let traversal_data_copy = traversal_data.clone();
scope.spawn(move |scope| { scope.spawn(move |scope| {
let n = nodes; let n = nodes;