revset: reimplement children to not scan visible ancestors twice

It's slightly faster, and removes the use of RevsetExpression::descendants()
API.
This commit is contained in:
Yuya Nishihara 2023-04-07 10:37:49 +09:00
parent 5dd99db250
commit adfd52445b
2 changed files with 42 additions and 51 deletions

View file

@ -284,45 +284,6 @@ fn predicate_fn_from_iter<'index, 'iter>(
}) })
} }
#[derive(Debug)]
struct ChildrenRevset<'index> {
// The revisions we want to find children for
root_set: Box<dyn InternalRevset<'index> + 'index>,
// Consider only candidates from this set
candidate_set: Box<dyn InternalRevset<'index> + 'index>,
}
impl<'index> InternalRevset<'index> for ChildrenRevset<'index> {
fn iter(&self) -> Box<dyn Iterator<Item = IndexEntry<'index>> + '_> {
let roots: HashSet<_> = self
.root_set
.iter()
.map(|parent| parent.position())
.collect();
Box::new(self.candidate_set.iter().filter(move |candidate| {
candidate
.parent_positions()
.iter()
.any(|parent_pos| roots.contains(parent_pos))
}))
}
fn into_predicate<'a>(self: Box<Self>) -> Box<dyn ToPredicateFn + 'a>
where
Self: 'a,
{
self
}
}
impl ToPredicateFn for ChildrenRevset<'_> {
fn to_predicate_fn(&self) -> Box<dyn FnMut(&IndexEntry<'_>) -> bool + '_> {
// TODO: can be optimized if candidate_set contains all heads
predicate_fn_from_iter(self.iter())
}
}
#[derive(Debug)] #[derive(Debug)]
struct FilterRevset<'index, P> { struct FilterRevset<'index, P> {
candidates: Box<dyn InternalRevset<'index> + 'index>, candidates: Box<dyn InternalRevset<'index> + 'index>,
@ -639,11 +600,20 @@ impl<'index, 'heads> EvaluationContext<'index, 'heads> {
} }
RevsetExpression::Children(roots) => { RevsetExpression::Children(roots) => {
let root_set = self.evaluate(roots)?; let root_set = self.evaluate(roots)?;
let candidates_expression = roots.descendants(); let head_set = self.revset_for_commit_ids(self.visible_heads);
let candidate_set = self.evaluate(&candidates_expression)?; let (walk, root_positions) = self.walk_ancestors_until_roots(&*root_set, &head_set);
Ok(Box::new(ChildrenRevset { let roots: HashSet<_> = root_positions.into_iter().collect();
root_set, let candidates = Box::new(RevWalkRevset { walk });
candidate_set, let predicate = pure_predicate_fn(move |entry| {
entry
.parent_positions()
.iter()
.any(|parent_pos| roots.contains(parent_pos))
});
// TODO: ToPredicateFn version can be optimized to only test the predicate()
Ok(Box::new(FilterRevset {
candidates,
predicate,
})) }))
} }
RevsetExpression::Ancestors { heads, generation } => { RevsetExpression::Ancestors { heads, generation } => {
@ -818,6 +788,29 @@ impl<'index, 'heads> EvaluationContext<'index, 'heads> {
self.composite_index.walk_revs(&head_ids, &[]) self.composite_index.walk_revs(&head_ids, &[])
} }
fn walk_ancestors_until_roots<'a, 'b, S, T>(
&self,
root_set: &S,
head_set: &T,
) -> (
impl Iterator<Item = IndexEntry<'index>> + Clone,
Vec<IndexPosition>,
)
where
S: InternalRevset<'a> + ?Sized,
T: InternalRevset<'b> + ?Sized,
{
// We can also make RevWalk stop visiting based on the generation number. Maybe
// it will perform better for unbalanced branchy history.
// https://github.com/martinvonz/jj/pull/1492#discussion_r1160678325
let root_positions = root_set.iter().map(|entry| entry.position()).collect_vec();
let bottom_position = *root_positions.last().unwrap_or(&IndexPosition::MAX);
let walk = self
.walk_ancestors(head_set)
.take_while(move |entry| entry.position() >= bottom_position);
(walk, root_positions)
}
/// Calculates `root_set:head_set`. /// Calculates `root_set:head_set`.
/// ///
/// The returned `HashSet` contains all `root_set` positions no matter if /// The returned `HashSet` contains all `root_set` positions no matter if
@ -832,15 +825,10 @@ impl<'index, 'heads> EvaluationContext<'index, 'heads> {
S: InternalRevset<'a> + ?Sized, S: InternalRevset<'a> + ?Sized,
T: InternalRevset<'b> + ?Sized, T: InternalRevset<'b> + ?Sized,
{ {
let root_positions = root_set.iter().map(|entry| entry.position()).collect_vec(); let (walk, root_positions) = self.walk_ancestors_until_roots(root_set, head_set);
let bottom_position = *root_positions.last().unwrap_or(&IndexPosition::MAX);
let mut reachable: HashSet<_> = root_positions.into_iter().collect(); let mut reachable: HashSet<_> = root_positions.into_iter().collect();
let mut index_entries = vec![]; let mut index_entries = vec![];
let candidates = self for candidate in walk.collect_vec().into_iter().rev() {
.walk_ancestors(head_set)
.take_while(|entry| entry.position() >= bottom_position)
.collect_vec();
for candidate in candidates.into_iter().rev() {
if reachable.contains(&candidate.position()) if reachable.contains(&candidate.position())
|| candidate || candidate
.parent_positions() .parent_positions()

View file

@ -803,6 +803,9 @@ fn test_evaluate_expression_children(use_git: bool) {
), ),
vec![commit5.id().clone()] vec![commit5.id().clone()]
); );
// Empty root
assert_eq!(resolve_commit_ids(mut_repo, "none()+"), vec![]);
} }
#[test_case(false ; "local backend")] #[test_case(false ; "local backend")]