ok/jj
1
0
Fork 0
forked from mirrors/jj

index: optimize heads_pos() to cache generation numbers during computation

Apparently, IndexEntry::generation_number() isn't cheap probably because it
involves random access to larger memory region, and the u32 value might not
be aligned. Let's instead store the generation numbers in BinaryHeap.

Also, heads_pos() becomes slightly faster by keeping the BinaryHeap entries
small, so I've removed the IndexEntry at all.

This makes the default log and disambiguation revsets fast, which evaluate
'heads(immutable_heads())'.

"bench revset" result in my linux repo:

    revsets/heads(tags())
    ---------------------
    baseline  3.28     560.6±4.01ms
    1         2.92     500.0±2.99ms
    2 (this)  1.98     339.6±1.64ms
This commit is contained in:
Yuya Nishihara 2023-11-15 10:17:49 +09:00
parent 1e933b84dd
commit 9832ee205d

View file

@ -967,7 +967,7 @@ impl<'a> CompositeIndex<'a> {
let entry = self.entry_by_pos(*pos);
min_generation = min(min_generation, entry.generation_number());
for parent_entry in entry.parents() {
work.push(IndexEntryByGeneration(parent_entry));
work.push(IndexPositionByGeneration::from(&parent_entry));
}
}
@ -975,16 +975,17 @@ impl<'a> CompositeIndex<'a> {
// set of candidates. Stop walking when we have gone past the minimum
// candidate generation.
let mut visited = HashSet::new();
while let Some(IndexEntryByGeneration(item)) = work.pop() {
while let Some(item) = work.pop() {
if !visited.insert(item.pos) {
continue;
}
if item.generation_number() < min_generation {
if item.generation < min_generation {
break;
}
candidate_positions.remove(&item.pos);
for parent_entry in item.parents() {
work.push(IndexEntryByGeneration(parent_entry));
let entry = self.entry_by_pos(item.pos);
for parent_entry in entry.parents() {
work.push(IndexPositionByGeneration::from(&parent_entry));
}
}
candidate_positions
@ -1126,6 +1127,33 @@ impl PartialOrd for IndexEntryByGeneration<'_> {
}
}
/// Wrapper to sort `IndexPosition` by its generation number.
///
/// This is similar to `IndexEntry` newtypes, but optimized for size and cache
/// locality. The original `IndexEntry` will have to be looked up when needed.
#[derive(Clone, Copy, Debug, Ord, PartialOrd)]
struct IndexPositionByGeneration {
generation: u32, // order by generation number
pos: IndexPosition, // tie breaker
}
impl Eq for IndexPositionByGeneration {}
impl PartialEq for IndexPositionByGeneration {
fn eq(&self, other: &Self) -> bool {
self.pos == other.pos
}
}
impl From<&IndexEntry<'_>> for IndexPositionByGeneration {
fn from(entry: &IndexEntry<'_>) -> Self {
IndexPositionByGeneration {
generation: entry.generation_number(),
pos: entry.position(),
}
}
}
trait RevWalkIndex<'a> {
type Entry: Clone + Ord + RevWalkIndexEntry<'a>;