History

feat: A PriorityQueue that is useful for graph traversal.

Sebastian Thiel committed 3 years ago

Commit dde8c3a1 parent fc423e43 files · +82 −14Browse files

Unified · Split

3 files
  1. gix-revision/src/describe.rs +14 −14
  2. gix-revision/src/lib.rs +8 −0
  3. gix-revision/src/queue.rs +60 −0
gix-revision/src/describe.rs+14−14
@@ -144,7 +144,7 @@ where · 143 unmodified lines
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;
@@ -152,7 +152,8 @@ pub(crate) mod function { · 1 unmodified line
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 { · 42 unmodified lines
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 { · 75 unmodified lines
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 { · 21 unmodified lines
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 { · 31 unmodified lines
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 { · 12 unmodified lines
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 { · 6 unmodified lines
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 { · 25 unmodified lines
429427 order: usize,
430428 }
431429 }
430+
431+type CommitTime = u32;
gix-revision/src/lib.rs+8−0
@@ -17,3 +17,11 @@ pub mod spec; · 16 unmodified lines
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+60−0
@@ -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+}