dag: implement fast path for children

Summary:
The fast path takes advantage of high-level segments and avoids visiting lower
level segments if possible.

`cargo bench --bench dag_ops children` before:

  children                                           16.842 s

after:

  children                                          427.577 ms

Reviewed By: markbt

Differential Revision: D16976385

fbshipit-source-id: ae1b5f26b6ce2213f5ce1f28c4ac1cf49678992f
This commit is contained in:
Jun Wu 2019-09-13 19:26:34 -07:00 committed by Facebook Github Bot
parent bf0277b8e2
commit 84361f6dfc
2 changed files with 26 additions and 11 deletions

View File

@ -546,20 +546,17 @@ impl Dag {
// The algorithm works as follows:
// - Iterate through level N segments [1].
// - Considering a level N segment S:
// Do S or parents of S overlap with `set`?
// - No: Skip S and check the next level N segment.
// - Yes: Is S a flat segment?
// Could we take the entire S?
// - If `set` covers `S - S.head + S.parents`, then yes, take S
// and continue with the next level N segment.
// Could we ignore the entire S and check the next level N segment?
// - If (S + S.parents) do not overlap with `set`, then yes, skip.
// No fast paths. Is S a flat segment?
// - No: Iterate through level N-1 segments covered by S,
// recursively (goto [1]).
// - Yes: Figure out children in the flat segment.
// Push them to the result.
// There could be a fast path if a high-level segment contains
// more information, like "Does this segment have roots without
// parents"? If that is marked "no", and the `set` covers
// `S - S.head + S.parents`. Then we can push S to result
// without checking lower level segments.
// FIXME: The algorithm relies on the fact that the highest level
// segments contain all known ids, which is not guaranteed in all
// setups (ex. build_high_level_segments can have "drop_last" set).
@ -579,6 +576,11 @@ impl Dag {
break;
}
let parents = seg.parents()?;
// Count of parents overlapping with `set`.
let overlapped_parents = parents.iter().filter(|p| ctx.set.contains(**p)).count();
// Remove the `high`. This segment cannot calculate
// `children(high)`. If `high` matches a `parent` of
// another segment, that segment will handle it.
@ -586,6 +588,19 @@ impl Dag {
.set
.intersection(&span.into())
.difference(&span.high.into());
if !seg.has_root()? {
// A segment must have at least one parent to be rootless.
debug_assert!(!parents.is_empty());
// Fast path: Take the segment directly.
if overlapped_parents == parents.len()
&& intersection.count() + 1 == span.count()
{
ctx.result.push_span(span);
continue;
}
}
if !intersection.is_empty() {
if level > 0 {
visit_segments(ctx, span, level - 1)?;
@ -601,7 +616,7 @@ impl Dag {
}
}
if seg.parents()?.into_iter().any(|p| ctx.set.contains(p)) {
if overlapped_parents > 0 {
if level > 0 {
visit_segments(ctx, span, level - 1)?;
} else {

View File

@ -62,7 +62,7 @@ impl Span {
Self { low, high }
}
fn count(self) -> u64 {
pub fn count(self) -> u64 {
self.high - self.low + 1
}