layout: Switch parallel selector matching over to using work stealing.

10% speedup over what we had before.
This commit is contained in:
Patrick Walton 2014-01-24 16:14:07 -08:00
parent d944567b9a
commit cb2e225b14
4 changed files with 81 additions and 60 deletions

View file

@ -8,19 +8,14 @@ use css::node_style::StyledNode;
use layout::incremental; use layout::incremental;
use layout::util::LayoutDataAccess; use layout::util::LayoutDataAccess;
use layout::wrapper::LayoutNode; use layout::wrapper::LayoutNode;
use servo_util::task::spawn_named;
use extra::arc::{Arc, RWArc}; use extra::arc::Arc;
use std::cast;
use std::libc::uintptr_t;
use std::rt;
use std::vec;
use style::{TNode, Stylist, cascade}; use style::{TNode, Stylist, cascade};
use style::{Before, After}; use style::{Before, After};
pub trait MatchMethods { pub trait MatchMethods {
fn match_node(&self, stylist: &Stylist); fn match_node(&self, stylist: &Stylist);
fn match_subtree(&self, stylist: RWArc<Stylist>); fn match_subtree(&self, stylist: &Stylist);
fn cascade_subtree(&self, parent: Option<LayoutNode>); fn cascade_subtree(&self, parent: Option<LayoutNode>);
} }
@ -47,54 +42,13 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
None => fail!("no layout data") None => fail!("no layout data")
} }
} }
fn match_subtree(&self, stylist: RWArc<Stylist>) {
let num_tasks = rt::default_sched_threads() * 2;
let mut node_count = 0;
let mut nodes_per_task = vec::from_elem(num_tasks, ~[]);
fn match_subtree(&self, stylist: &Stylist) {
for node in self.traverse_preorder() { for node in self.traverse_preorder() {
if node.is_element() { if node.is_element() {
nodes_per_task[node_count % num_tasks].push(node); node.match_node(stylist);
node_count += 1;
} }
} }
let (port, chan) = SharedChan::new();
let mut num_spawned = 0;
for nodes in nodes_per_task.move_iter() {
if nodes.len() > 0 {
let chan = chan.clone();
let stylist = stylist.clone();
// FIXME(pcwalton): This transmute is to work around the fact that we have no
// mechanism for safe fork/join parallelism. If we had such a thing, then we could
// close over the lifetime-bounded `LayoutNode`. But we can't, so we force it with
// a transmute.
let evil: uintptr_t = unsafe {
cast::transmute(nodes)
};
let evil = Some(evil);
spawn_named("MatchMethods for LayoutNode", proc() {
let mut evil = evil;
let nodes: ~[LayoutNode] = unsafe {
cast::transmute(evil.take_unwrap())
};
stylist.read(|stylist| {
for node in nodes.iter() {
node.match_node(stylist);
}
});
chan.send(());
});
num_spawned += 1;
}
}
for _ in range(0, num_spawned) {
port.recv();
}
} }
fn cascade_subtree(&self, parent: Option<LayoutNode>) { fn cascade_subtree(&self, parent: Option<LayoutNode>) {

View file

@ -18,6 +18,7 @@ use gfx::font_context::{FontContext, FontContextInfo};
use servo_msg::constellation_msg::ConstellationChan; use servo_msg::constellation_msg::ConstellationChan;
use servo_net::local_image_cache::LocalImageCache; use servo_net::local_image_cache::LocalImageCache;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use style::Stylist;
#[thread_local] #[thread_local]
static mut FONT_CONTEXT: *mut FontContext = 0 as *mut FontContext; static mut FONT_CONTEXT: *mut FontContext = 0 as *mut FontContext;
@ -39,6 +40,11 @@ pub struct LayoutContext {
/// Information needed to construct a font context. /// Information needed to construct a font context.
font_context_info: FontContextInfo, font_context_info: FontContextInfo,
/// The CSS selector stylist.
///
/// FIXME(pcwalton): Make this no longer an unsafe pointer once we have fast `RWArc`s.
stylist: *Stylist,
} }
impl LayoutContext { impl LayoutContext {

View file

@ -22,7 +22,7 @@ use layout::parallel;
use layout::util::{LayoutDataAccess, OpaqueNode, LayoutDataWrapper}; use layout::util::{LayoutDataAccess, OpaqueNode, LayoutDataWrapper};
use layout::wrapper::LayoutNode; use layout::wrapper::LayoutNode;
use extra::arc::{Arc, MutexArc, RWArc}; use extra::arc::{Arc, MutexArc};
use geom::rect::Rect; use geom::rect::Rect;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::display_list::{ClipDisplayItemClass, DisplayItem, DisplayItemIterator, DisplayList}; use gfx::display_list::{ClipDisplayItemClass, DisplayItem, DisplayItemIterator, DisplayList};
@ -91,7 +91,7 @@ pub struct LayoutTask {
/// A cached display list. /// A cached display list.
display_list: Option<Arc<DisplayList<OpaqueNode>>>, display_list: Option<Arc<DisplayList<OpaqueNode>>>,
stylist: RWArc<Stylist>, stylist: ~Stylist,
/// The workers that we use for parallel operation. /// The workers that we use for parallel operation.
parallel_traversal: Option<WorkQueue<*mut LayoutContext,UnsafeFlow>>, parallel_traversal: Option<WorkQueue<*mut LayoutContext,UnsafeFlow>>,
@ -274,7 +274,7 @@ impl LayoutTask {
leaf_set: MutexArc::new(LeafSet::new()), leaf_set: MutexArc::new(LeafSet::new()),
display_list: None, display_list: None,
stylist: RWArc::new(new_stylist()), stylist: ~new_stylist(),
parallel_traversal: parallel_traversal, parallel_traversal: parallel_traversal,
profiler_chan: profiler_chan, profiler_chan: profiler_chan,
opts: opts.clone() opts: opts.clone()
@ -302,6 +302,7 @@ impl LayoutTask {
constellation_chan: self.constellation_chan.clone(), constellation_chan: self.constellation_chan.clone(),
leaf_set: self.leaf_set.clone(), leaf_set: self.leaf_set.clone(),
font_context_info: font_context_info, font_context_info: font_context_info,
stylist: &*self.stylist,
} }
} }
@ -379,10 +380,7 @@ impl LayoutTask {
} }
fn handle_add_stylesheet(&mut self, sheet: Stylesheet) { fn handle_add_stylesheet(&mut self, sheet: Stylesheet) {
let mut sheet = Some(sheet); self.stylist.add_stylesheet(sheet, AuthorOrigin)
self.stylist.write(|stylist| {
stylist.add_stylesheet(sheet.take_unwrap(), AuthorOrigin);
});
} }
/// Builds the flow tree. /// Builds the flow tree.
@ -521,7 +519,12 @@ impl LayoutTask {
ReflowDocumentDamage => {} ReflowDocumentDamage => {}
_ => { _ => {
profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone(), || { profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone(), || {
node.match_subtree(self.stylist.clone()); match self.parallel_traversal {
None => node.match_subtree(self.stylist),
Some(ref mut traversal) => {
parallel::match_subtree(node, &mut layout_ctx, traversal)
}
}
}); });
profile(time::LayoutSelectorCascadeCategory, self.profiler_chan.clone(), || { profile(time::LayoutSelectorCascadeCategory, self.profiler_chan.clone(), || {
node.cascade_subtree(None); node.cascade_subtree(None);

View file

@ -2,19 +2,25 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Implements parallel traversals over the flow tree. //! Implements parallel traversals over the DOM and flow trees.
//!
//! This code is highly unsafe. Keep this file small and easy to audit.
use css::matching::MatchMethods;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::flow::{Flow, LeafSet, PostorderFlowTraversal}; use layout::flow::{Flow, LeafSet, PostorderFlowTraversal};
use layout::flow; use layout::flow;
use layout::layout_task::{AssignHeightsAndStoreOverflowTraversal, BubbleWidthsTraversal}; use layout::layout_task::{AssignHeightsAndStoreOverflowTraversal, BubbleWidthsTraversal};
use layout::wrapper::LayoutNode;
use extra::arc::MutexArc; use extra::arc::MutexArc;
use servo_util::time::{ProfilerChan, profile}; use servo_util::time::{ProfilerChan, profile};
use servo_util::time; use servo_util::time;
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
use std::cast; use std::cast;
use std::ptr;
use std::sync::atomics::{AtomicInt, Relaxed, SeqCst}; use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
use style::{Stylist, TNode};
pub enum TraversalKind { pub enum TraversalKind {
BubbleWidthsTraversalKind, BubbleWidthsTraversalKind,
@ -39,6 +45,14 @@ pub fn mut_owned_flow_to_unsafe_flow(flow: *mut ~Flow) -> UnsafeFlow {
} }
} }
pub type UnsafeLayoutNode = (uint, uint);
fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode {
unsafe {
cast::transmute_copy(node)
}
}
/// Information that we need stored in each flow. /// Information that we need stored in each flow.
pub struct FlowParallelInfo { pub struct FlowParallelInfo {
/// The number of children that still need work done. /// The number of children that still need work done.
@ -101,6 +115,30 @@ impl<'a> ParallelPostorderFlowTraversal for BubbleWidthsTraversal<'a> {}
impl<'a> ParallelPostorderFlowTraversal for AssignHeightsAndStoreOverflowTraversal<'a> {} impl<'a> ParallelPostorderFlowTraversal for AssignHeightsAndStoreOverflowTraversal<'a> {}
fn match_node(unsafe_layout_node: UnsafeLayoutNode,
proxy: &mut WorkerProxy<*mut LayoutContext,UnsafeLayoutNode>) {
unsafe {
let layout_context: &mut LayoutContext = cast::transmute(*proxy.user_data());
// Get a real layout node.
let node: LayoutNode = cast::transmute(unsafe_layout_node);
// Enqueue kids.
for kid in node.children() {
if kid.is_element() {
proxy.push(WorkUnit {
fun: match_node,
data: layout_node_to_unsafe_layout_node(&kid),
});
}
}
// Perform the CSS selector matching.
let stylist: &Stylist = cast::transmute(layout_context.stylist);
node.match_node(stylist);
}
}
fn bubble_widths(unsafe_flow: UnsafeFlow, proxy: &mut WorkerProxy<*mut LayoutContext,UnsafeFlow>) { fn bubble_widths(unsafe_flow: UnsafeFlow, proxy: &mut WorkerProxy<*mut LayoutContext,UnsafeFlow>) {
let layout_context: &mut LayoutContext = unsafe { let layout_context: &mut LayoutContext = unsafe {
cast::transmute(*proxy.user_data()) cast::transmute(*proxy.user_data())
@ -122,6 +160,24 @@ fn assign_heights_and_store_overflow(unsafe_flow: UnsafeFlow,
assign_heights_traversal.run_parallel(unsafe_flow) assign_heights_traversal.run_parallel(unsafe_flow)
} }
pub fn match_subtree(root_node: &LayoutNode,
layout_context: &mut LayoutContext,
queue: &mut WorkQueue<*mut LayoutContext,UnsafeLayoutNode>) {
unsafe {
queue.data = cast::transmute(layout_context)
}
// Enqueue the root node.
queue.push(WorkUnit {
fun: match_node,
data: layout_node_to_unsafe_layout_node(root_node),
});
queue.run();
queue.data = ptr::mut_null()
}
pub fn traverse_flow_tree(kind: TraversalKind, pub fn traverse_flow_tree(kind: TraversalKind,
leaf_set: &MutexArc<LeafSet>, leaf_set: &MutexArc<LeafSet>,
profiler_chan: ProfilerChan, profiler_chan: ProfilerChan,
@ -147,6 +203,8 @@ pub fn traverse_flow_tree(kind: TraversalKind,
}) })
}); });
queue.run() queue.run();
queue.data = ptr::mut_null()
} }