Skip to content

Commit dde8c3a

Browse files
committed
feat: A PriorityQueue that is useful for graph traversal.
1 parent fc423e4 commit dde8c3a

File tree

3 files changed

+82
-14
lines changed

3 files changed

+82
-14
lines changed

gix-revision/src/describe.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,16 @@ where
144144
}
145145

146146
pub(crate) mod function {
147-
use std::{borrow::Cow, cmp::Ordering, collections::VecDeque, iter::FromIterator};
147+
use std::{borrow::Cow, cmp::Ordering};
148148

149149
use bstr::BStr;
150150
use gix_hash::oid;
151151
use gix_hashtable::{hash_map, HashMap};
152152
use gix_object::CommitRefIter;
153153

154154
use super::{Error, Outcome};
155-
use crate::describe::{Flags, Options, MAX_CANDIDATES};
155+
use crate::describe::{CommitTime, Flags, Options, MAX_CANDIDATES};
156+
use crate::PriorityQueue;
156157

157158
/// Given a `commit` id, traverse the commit graph and collect candidate names from the `name_by_oid` mapping to produce
158159
/// an `Outcome`, which converted [`into_format()`][Outcome::into_format()] will produce a typical `git describe` string.
@@ -201,14 +202,14 @@ pub(crate) mod function {
201202
let mut buf = Vec::new();
202203
let mut parent_buf = Vec::new();
203204

204-
let mut queue = VecDeque::from_iter(Some((commit.to_owned(), u32::MAX)));
205+
let mut queue = PriorityQueue::from_iter(Some((u32::MAX, commit.to_owned())));
205206
let mut candidates = Vec::new();
206207
let mut commits_seen = 0;
207208
let mut gave_up_on_commit = None;
208209
let mut seen = HashMap::<gix_hash::ObjectId, Flags>::default();
209210
seen.insert(commit.to_owned(), 0u32);
210211

211-
while let Some((commit, _commit_time)) = queue.pop_front() {
212+
while let Some(commit) = queue.pop() {
212213
commits_seen += 1;
213214
if let Some(name) = name_by_oid.get(&commit) {
214215
if candidates.len() < max_candidates {
@@ -290,7 +291,7 @@ pub(crate) mod function {
290291
});
291292

292293
if let Some(commit_id) = gave_up_on_commit {
293-
queue.push_front((commit_id, u32::MAX));
294+
queue.insert(u32::MAX, commit_id);
294295
commits_seen -= 1;
295296
}
296297

@@ -318,7 +319,7 @@ pub(crate) mod function {
318319
find: &mut Find,
319320
buf: &mut Vec<u8>,
320321
parent_buf: &mut Vec<u8>,
321-
queue: &mut VecDeque<(gix_hash::ObjectId, u32)>,
322+
queue: &mut PriorityQueue<CommitTime, gix_hash::ObjectId>,
322323
seen: &mut HashMap<gix_hash::ObjectId, Flags>,
323324
commit: &gix_hash::oid,
324325
commit_flags: Flags,
@@ -356,10 +357,7 @@ pub(crate) mod function {
356357
.unwrap_or_default();
357358

358359
entry.insert(commit_flags);
359-
match queue.binary_search_by(|c| c.1.cmp(&parent_commit_date).reverse()) {
360-
Ok(_) => queue.push_back((parent_id, parent_commit_date)),
361-
Err(pos) => queue.insert(pos, (parent_id, parent_commit_date)),
362-
};
360+
queue.insert(parent_commit_date, parent_id);
363361
}
364362
hash_map::Entry::Occupied(mut entry) => {
365363
*entry.get_mut() |= commit_flags;
@@ -378,7 +376,7 @@ pub(crate) mod function {
378376

379377
#[allow(clippy::too_many_arguments)]
380378
fn finish_depth_computation<'name, Find, E>(
381-
mut queue: VecDeque<(gix_hash::ObjectId, u32)>,
379+
mut queue: PriorityQueue<CommitTime, gix_hash::ObjectId>,
382380
mut find: Find,
383381
best_candidate: &mut Candidate<'name>,
384382
mut seen: HashMap<gix_hash::ObjectId, Flags>,
@@ -391,13 +389,13 @@ pub(crate) mod function {
391389
E: std::error::Error + Send + Sync + 'static,
392390
{
393391
let mut commits_seen = 0;
394-
while let Some((commit, _commit_time)) = queue.pop_front() {
392+
while let Some(commit) = queue.pop() {
395393
commits_seen += 1;
396394
let flags = seen[&commit];
397395
if (flags & best_candidate.identity_bit) == best_candidate.identity_bit {
398396
if queue
399-
.iter()
400-
.all(|(id, _)| (seen[id] & best_candidate.identity_bit) == best_candidate.identity_bit)
397+
.iter_random()
398+
.all(|id| (seen[id] & best_candidate.identity_bit) == best_candidate.identity_bit)
401399
{
402400
break;
403401
}
@@ -429,3 +427,5 @@ pub(crate) mod function {
429427
order: usize,
430428
}
431429
}
430+
431+
type CommitTime = u32;

gix-revision/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ pub mod spec;
1717

1818
mod types;
1919
pub use types::Spec;
20+
21+
/// A utility type implementing a queue which can be used to automatically sort data by its time in ascending order.
22+
///
23+
/// Note that the performance of this queue is very relevant to overall algorithm performance of many graph-walking algorithms,
24+
/// and as it stands our implementation is about 6% slower in practice, probably also depending on the size of the stored data.
25+
#[derive(Default)]
26+
pub struct PriorityQueue<K: Ord, T>(std::collections::BinaryHeap<queue::Item<K, T>>);
27+
mod queue;

gix-revision/src/queue.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use crate::PriorityQueue;
2+
use std::cmp::Ordering;
3+
use std::collections::BinaryHeap;
4+
5+
pub(crate) struct Item<K, T> {
6+
key: K,
7+
value: T,
8+
}
9+
10+
impl<K: Ord, T> PartialEq<Self> for Item<K, T> {
11+
fn eq(&self, other: &Self) -> bool {
12+
Ord::cmp(self, other).is_eq()
13+
}
14+
}
15+
16+
impl<K: Ord, T> Eq for Item<K, T> {}
17+
18+
impl<K: Ord, T> PartialOrd<Self> for Item<K, T> {
19+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20+
Ord::cmp(self, other).into()
21+
}
22+
}
23+
24+
impl<K: Ord, T> Ord for Item<K, T> {
25+
fn cmp(&self, other: &Self) -> Ordering {
26+
self.key.cmp(&other.key)
27+
}
28+
}
29+
30+
impl<K: Ord, T> PriorityQueue<K, T> {
31+
/// Insert `value` so that it is ordered according to `key`.
32+
pub fn insert(&mut self, key: K, value: T) {
33+
self.0.push(Item { key, value });
34+
}
35+
36+
/// Pop the highest-priority item off the queue.
37+
pub fn pop(&mut self) -> Option<T> {
38+
self.0.pop().map(|t| t.value)
39+
}
40+
41+
/// Iterate all items ordered from highest to lowest priority.
42+
pub fn iter_random(&self) -> impl Iterator<Item = &T> {
43+
self.0.iter().map(|t| &t.value)
44+
}
45+
46+
/// Return true if the queue is empty.
47+
pub fn is_empty(&self) -> bool {
48+
self.0.is_empty()
49+
}
50+
}
51+
52+
impl<K: Ord, T> FromIterator<(K, T)> for PriorityQueue<K, T> {
53+
fn from_iter<I: IntoIterator<Item = (K, T)>>(iter: I) -> Self {
54+
let mut q = PriorityQueue(BinaryHeap::new());
55+
for (k, v) in iter {
56+
q.insert(k, v);
57+
}
58+
q
59+
}
60+
}

0 commit comments

Comments
 (0)