1
Fork 0

Move a Node's parent into the descendents list.

`Node` has an optional parent and a list of other descendents. Most of
the time the parent is treated the same as the other descendents --
error-handling is the exception -- and chaining them together for
iteration has a non-trivial cost.

This commit changes the representation. There is now a single list of
descendants, and a boolean flag that indicates if there is a parent (in
which case it is first descendent). This representation encodes the same
information, in a way that is less idiomatic but cheaper to iterate over
for the common case where the parent doesn't need special treatment.

As a result, some benchmark workloads are up to 2% faster.
This commit is contained in:
Nicholas Nethercote 2019-09-17 12:08:24 +10:00
parent 5670d048c0
commit 476e75ded7
2 changed files with 36 additions and 43 deletions

View file

@ -74,9 +74,7 @@ impl<'a, O: ForestObligation + 'a> dot::GraphWalk<'a> for &'a ObligationForest<O
.flat_map(|i| { .flat_map(|i| {
let node = &self.nodes[i]; let node = &self.nodes[i];
node.parent.iter() node.dependents.iter().map(move |p| (p.index(), i))
.chain(node.dependents.iter())
.map(move |p| (p.index(), i))
}) })
.collect() .collect()
} }

View file

@ -177,21 +177,17 @@ struct Node<O> {
obligation: O, obligation: O,
state: Cell<NodeState>, state: Cell<NodeState>,
/// The parent of a node - the original obligation of which it is a
/// subobligation. Except for error reporting, it is just like any member
/// of `dependents`.
///
/// Unlike `ObligationForest::nodes`, this uses `NodeIndex` rather than
/// `usize` for the index, because keeping the size down is more important
/// than the cost of converting to a `usize` for indexing.
parent: Option<NodeIndex>,
/// Obligations that depend on this obligation for their completion. They /// Obligations that depend on this obligation for their completion. They
/// must all be in a non-pending state. /// must all be in a non-pending state.
///
/// This uses `NodeIndex` for the same reason as `parent`.
dependents: Vec<NodeIndex>, dependents: Vec<NodeIndex>,
/// If true, dependents[0] points to a "parent" node, which requires
/// special treatment upon error but is otherwise treated the same.
/// (It would be more idiomatic to store the parent node in a separate
/// `Option<NodeIndex>` field, but that slows down the common case of
/// iterating over the parent and other descendants together.)
has_parent: bool,
/// Identifier of the obligation tree to which this node belongs. /// Identifier of the obligation tree to which this node belongs.
obligation_tree_id: ObligationTreeId, obligation_tree_id: ObligationTreeId,
} }
@ -205,8 +201,13 @@ impl<O> Node<O> {
Node { Node {
obligation, obligation,
state: Cell::new(NodeState::Pending), state: Cell::new(NodeState::Pending),
parent, dependents:
dependents: vec![], if let Some(parent_index) = parent {
vec![parent_index]
} else {
vec![]
},
has_parent: parent.is_some(),
obligation_tree_id, obligation_tree_id,
} }
} }
@ -315,13 +316,11 @@ impl<O: ForestObligation> ObligationForest<O> {
obligation, parent, o.get()); obligation, parent, o.get());
let node = &mut self.nodes[o.get().index()]; let node = &mut self.nodes[o.get().index()];
if let Some(parent_index) = parent { if let Some(parent_index) = parent {
// If the node is already in `waiting_cache`, it's already // If the node is already in `waiting_cache`, it has
// been marked with a parent. (It's possible that parent // already had its chance to be marked with a parent. So if
// has been cleared by `apply_rewrites`, though.) So just // it's not already present, just dump `parent` into the
// dump `parent` into `node.dependents`... unless it's // dependents as a non-parent.
// already in `node.dependents` or `node.parent`. if !node.dependents.contains(&parent_index) {
if !node.dependents.contains(&parent_index) &&
Some(parent_index) != node.parent {
node.dependents.push(parent_index); node.dependents.push(parent_index);
} }
} }
@ -523,7 +522,7 @@ impl<O: ForestObligation> ObligationForest<O> {
NodeState::Success => { NodeState::Success => {
node.state.set(NodeState::OnDfsStack); node.state.set(NodeState::OnDfsStack);
stack.push(i); stack.push(i);
for index in node.parent.iter().chain(node.dependents.iter()) { for index in node.dependents.iter() {
self.find_cycles_from_node(stack, processor, index.index()); self.find_cycles_from_node(stack, processor, index.index());
} }
stack.pop(); stack.pop();
@ -549,12 +548,15 @@ impl<O: ForestObligation> ObligationForest<O> {
let node = &self.nodes[i]; let node = &self.nodes[i];
node.state.set(NodeState::Error); node.state.set(NodeState::Error);
trace.push(node.obligation.clone()); trace.push(node.obligation.clone());
error_stack.extend(node.dependents.iter().map(|index| index.index())); if node.has_parent {
// The first dependent is the parent, which is treated
// Loop to the parent. // specially.
match node.parent { error_stack.extend(node.dependents.iter().skip(1).map(|index| index.index()));
Some(parent_index) => i = parent_index.index(), i = node.dependents[0].index();
None => break } else {
// No parent; treat all dependents non-specially.
error_stack.extend(node.dependents.iter().map(|index| index.index()));
break;
} }
} }
@ -565,9 +567,7 @@ impl<O: ForestObligation> ObligationForest<O> {
_ => node.state.set(NodeState::Error), _ => node.state.set(NodeState::Error),
} }
error_stack.extend( error_stack.extend(node.dependents.iter().map(|index| index.index()));
node.parent.iter().chain(node.dependents.iter()).map(|index| index.index())
);
} }
self.scratch.replace(error_stack); self.scratch.replace(error_stack);
@ -577,7 +577,7 @@ impl<O: ForestObligation> ObligationForest<O> {
// This always-inlined function is for the hot call site. // This always-inlined function is for the hot call site.
#[inline(always)] #[inline(always)]
fn inlined_mark_neighbors_as_waiting_from(&self, node: &Node<O>) { fn inlined_mark_neighbors_as_waiting_from(&self, node: &Node<O>) {
for dependent in node.parent.iter().chain(node.dependents.iter()) { for dependent in node.dependents.iter() {
self.mark_as_waiting_from(&self.nodes[dependent.index()]); self.mark_as_waiting_from(&self.nodes[dependent.index()]);
} }
} }
@ -706,20 +706,15 @@ impl<O: ForestObligation> ObligationForest<O> {
let nodes_len = node_rewrites.len(); let nodes_len = node_rewrites.len();
for node in &mut self.nodes { for node in &mut self.nodes {
if let Some(index) = node.parent {
let new_i = node_rewrites[index.index()];
if new_i >= nodes_len {
node.parent = None;
} else {
node.parent = Some(NodeIndex::new(new_i));
}
}
let mut i = 0; let mut i = 0;
while i < node.dependents.len() { while i < node.dependents.len() {
let new_i = node_rewrites[node.dependents[i].index()]; let new_i = node_rewrites[node.dependents[i].index()];
if new_i >= nodes_len { if new_i >= nodes_len {
node.dependents.swap_remove(i); node.dependents.swap_remove(i);
if i == 0 && node.has_parent {
// We just removed the parent.
node.has_parent = false;
}
} else { } else {
node.dependents[i] = NodeIndex::new(new_i); node.dependents[i] = NodeIndex::new(new_i);
i += 1; i += 1;