Skip to content

Commit 56b3939

Browse files
committed
index: heads_pos(): push parents to queue while finding heads
Instead of immediately populating the queue with the parents of all candidate commits, we can push the parents as we iterate over candidate commits, which seems to improve performance slightly. Benchmark results on Git repo: ``` revsets/heads(tags()) 28.238% improvement revsets/heads(::v2.40.0) 50.456% improvement ```
1 parent 8739bdf commit 56b3939

File tree

1 file changed

+49
-57
lines changed

1 file changed

+49
-57
lines changed

lib/src/default_index/composite.rs

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#![allow(missing_docs)]
1616

1717
use std::cmp::max;
18-
use std::cmp::min;
1918
use std::cmp::Ordering;
2019
use std::collections::binary_heap;
2120
use std::collections::BTreeSet;
@@ -318,32 +317,13 @@ impl CompositeIndex {
318317
set1: &[IndexPosition],
319318
set2: &[IndexPosition],
320319
) -> BTreeSet<IndexPosition> {
321-
fn shift_to_parents(
322-
index: &CompositeIndex,
323-
items: &mut BinaryHeap<IndexPosition>,
324-
cur_pos: IndexPosition,
325-
) {
326-
let mut parent_positions = index.entry_by_pos(cur_pos).parent_positions().into_iter();
327-
if let Some(parent_pos) = parent_positions.next() {
328-
assert!(parent_pos < cur_pos);
329-
dedup_replace(items, parent_pos).unwrap();
330-
} else {
331-
dedup_pop(items).unwrap();
332-
return;
333-
}
334-
for parent_pos in parent_positions {
335-
assert!(parent_pos < cur_pos);
336-
items.push(parent_pos);
337-
}
338-
}
339-
340320
let mut items1: BinaryHeap<_> = set1.iter().copied().collect();
341321
let mut items2: BinaryHeap<_> = set2.iter().copied().collect();
342322
let mut result = BTreeSet::new();
343323
while let (Some(&pos1), Some(&pos2)) = (items1.peek(), items2.peek()) {
344324
match pos1.cmp(&pos2) {
345-
Ordering::Greater => shift_to_parents(self, &mut items1, pos1),
346-
Ordering::Less => shift_to_parents(self, &mut items2, pos2),
325+
Ordering::Greater => shift_to_parents(&mut items1, &self.entry_by_pos(pos1)),
326+
Ordering::Less => shift_to_parents(&mut items2, &self.entry_by_pos(pos2)),
347327
Ordering::Equal => {
348328
result.insert(pos1);
349329
dedup_pop(&mut items1).unwrap();
@@ -380,44 +360,40 @@ impl CompositeIndex {
380360
/// entries that are heads in the repository.
381361
pub fn heads_pos(
382362
&self,
383-
mut candidate_positions: BTreeSet<IndexPosition>,
363+
candidate_positions: BTreeSet<IndexPosition>,
384364
) -> BTreeSet<IndexPosition> {
385-
// Add all parents of the candidates to the work queue. The parents and their
386-
// ancestors are not heads.
387-
// Also find the smallest generation number among the candidates.
388-
let mut work = Vec::new();
389-
let mut min_generation = u32::MAX;
390-
for pos in &candidate_positions {
391-
let entry = self.entry_by_pos(*pos);
392-
min_generation = min(min_generation, entry.generation_number());
393-
work.extend(entry.parent_positions());
394-
}
395-
let mut work = BinaryHeap::from(work);
396-
397-
// Walk ancestors of the parents of the candidates. Remove visited commits from
398-
// set of candidates. Stop walking when we have gone past the minimum
399-
// candidate generation.
400-
while let Some(&cur_pos) = work.peek() {
401-
candidate_positions.remove(&cur_pos);
402-
let entry = self.entry_by_pos(cur_pos);
403-
if entry.generation_number() <= min_generation {
404-
dedup_pop(&mut work).unwrap();
405-
continue;
406-
}
407-
let mut parent_positions = entry.parent_positions().into_iter();
408-
if let Some(parent_pos) = parent_positions.next() {
409-
assert!(parent_pos < cur_pos);
410-
dedup_replace(&mut work, parent_pos).unwrap();
411-
} else {
412-
dedup_pop(&mut work).unwrap();
413-
continue;
414-
}
415-
for parent_pos in parent_positions {
416-
assert!(parent_pos < cur_pos);
417-
work.push(parent_pos);
365+
let Some(min_generation) = candidate_positions
366+
.iter()
367+
.map(|&pos| self.entry_by_pos(pos).generation_number())
368+
.min()
369+
else {
370+
return BTreeSet::new();
371+
};
372+
373+
// Iterate though the candidates by reverse index position, keeping track of the
374+
// ancestors of already-found heads. If a candidate is an ancestor of an
375+
// already-found head, then it can be removed.
376+
let mut parents = BinaryHeap::new();
377+
let mut heads = BTreeSet::new();
378+
'outer: for candidate in candidate_positions.into_iter().rev() {
379+
while let Some(&parent) = parents.peek().filter(|&&parent| parent >= candidate) {
380+
let entry = self.entry_by_pos(parent);
381+
if entry.generation_number() <= min_generation {
382+
dedup_pop(&mut parents).unwrap();
383+
} else {
384+
shift_to_parents(&mut parents, &entry);
385+
}
386+
if parent == candidate {
387+
// The candidate is an ancestor of an existing head, so we can skip it.
388+
continue 'outer;
389+
}
418390
}
391+
// No parents matched, so this commit is a head.
392+
let entry = self.entry_by_pos(candidate);
393+
parents.extend(entry.parent_positions());
394+
heads.insert(candidate);
419395
}
420-
candidate_positions
396+
heads
421397
}
422398

423399
pub(super) fn evaluate_revset(
@@ -595,6 +571,22 @@ pub struct IndexStats {
595571
pub levels: Vec<IndexLevelStats>,
596572
}
597573

574+
/// Removes an entry from the queue and replace it with its parents.
575+
fn shift_to_parents(items: &mut BinaryHeap<IndexPosition>, entry: &IndexEntry) {
576+
let mut parent_positions = entry.parent_positions().into_iter();
577+
if let Some(parent_pos) = parent_positions.next() {
578+
assert!(parent_pos < entry.position());
579+
dedup_replace(items, parent_pos).unwrap();
580+
} else {
581+
dedup_pop(items).unwrap();
582+
return;
583+
}
584+
for parent_pos in parent_positions {
585+
assert!(parent_pos < entry.position());
586+
items.push(parent_pos);
587+
}
588+
}
589+
598590
/// Removes the greatest items (including duplicates) from the heap, returns
599591
/// one.
600592
fn dedup_pop<T: Ord>(heap: &mut BinaryHeap<T>) -> Option<T> {

0 commit comments

Comments
 (0)