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
144}144}
145 145
146pub(crate) mod function {146pub(crate) mod function {
147 use std::{borrow::Cow, cmp::Ordering, collections::VecDeque, iter::FromIterator};147 use std::{borrow::Cow, cmp::Ordering};
148 148
149 use bstr::BStr;149 use bstr::BStr;
150 use gix_hash::oid;150 use gix_hash::oid;
@@ -152,7 +152,8 @@ pub(crate) mod function { · 1 unmodified line
152 use gix_object::CommitRefIter;152 use gix_object::CommitRefIter;
153 153
154 use super::{Error, Outcome};154 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;
156 157
157 /// Given a `commit` id, traverse the commit graph and collect candidate names from the `name_by_oid` mapping to produce158 /// Given a `commit` id, traverse the commit graph and collect candidate names from the `name_by_oid` mapping to produce
158 /// an `Outcome`, which converted [`into_format()`][Outcome::into_format()] will produce a typical `git describe` string.159 /// 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
201 let mut buf = Vec::new();202 let mut buf = Vec::new();
202 let mut parent_buf = Vec::new();203 let mut parent_buf = Vec::new();
203 204
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())));
205 let mut candidates = Vec::new();206 let mut candidates = Vec::new();
206 let mut commits_seen = 0;207 let mut commits_seen = 0;
207 let mut gave_up_on_commit = None;208 let mut gave_up_on_commit = None;
208 let mut seen = HashMap::<gix_hash::ObjectId, Flags>::default();209 let mut seen = HashMap::<gix_hash::ObjectId, Flags>::default();
209 seen.insert(commit.to_owned(), 0u32);210 seen.insert(commit.to_owned(), 0u32);
210 211
211 while let Some((commit, _commit_time)) = queue.pop_front() {212 while let Some(commit) = queue.pop() {
212 commits_seen += 1;213 commits_seen += 1;
213 if let Some(name) = name_by_oid.get(&commit) {214 if let Some(name) = name_by_oid.get(&commit) {
214 if candidates.len() < max_candidates {215 if candidates.len() < max_candidates {
@@ -290,7 +291,7 @@ pub(crate) mod function { · 75 unmodified lines
290 });291 });
291 292
292 if let Some(commit_id) = gave_up_on_commit {293 if let Some(commit_id) = gave_up_on_commit {
293 queue.push_front((commit_id, u32::MAX));294 queue.insert(u32::MAX, commit_id);
294 commits_seen -= 1;295 commits_seen -= 1;
295 }296 }
296 297
@@ -318,7 +319,7 @@ pub(crate) mod function { · 21 unmodified lines
318 find: &mut Find,319 find: &mut Find,
319 buf: &mut Vec<u8>,320 buf: &mut Vec<u8>,
320 parent_buf: &mut Vec<u8>,321 parent_buf: &mut Vec<u8>,
321 queue: &mut VecDeque<(gix_hash::ObjectId, u32)>,322 queue: &mut PriorityQueue<CommitTime, gix_hash::ObjectId>,
322 seen: &mut HashMap<gix_hash::ObjectId, Flags>,323 seen: &mut HashMap<gix_hash::ObjectId, Flags>,
323 commit: &gix_hash::oid,324 commit: &gix_hash::oid,
324 commit_flags: Flags,325 commit_flags: Flags,
@@ -356,10 +357,7 @@ pub(crate) mod function { · 31 unmodified lines
356 .unwrap_or_default();357 .unwrap_or_default();
357 358
358 entry.insert(commit_flags);359 entry.insert(commit_flags);
359 match queue.binary_search_by(|c| c.1.cmp(&parent_commit_date).reverse()) {360 queue.insert(parent_commit_date, parent_id);
360 Ok(_) => queue.push_back((parent_id, parent_commit_date)),
361 Err(pos) => queue.insert(pos, (parent_id, parent_commit_date)),
362 };
363 }361 }
364 hash_map::Entry::Occupied(mut entry) => {362 hash_map::Entry::Occupied(mut entry) => {
365 *entry.get_mut() |= commit_flags;363 *entry.get_mut() |= commit_flags;
@@ -378,7 +376,7 @@ pub(crate) mod function { · 12 unmodified lines
378 376
379 #[allow(clippy::too_many_arguments)]377 #[allow(clippy::too_many_arguments)]
380 fn finish_depth_computation<'name, Find, E>(378 fn finish_depth_computation<'name, Find, E>(
381 mut queue: VecDeque<(gix_hash::ObjectId, u32)>,379 mut queue: PriorityQueue<CommitTime, gix_hash::ObjectId>,
382 mut find: Find,380 mut find: Find,
383 best_candidate: &mut Candidate<'name>,381 best_candidate: &mut Candidate<'name>,
384 mut seen: HashMap<gix_hash::ObjectId, Flags>,382 mut seen: HashMap<gix_hash::ObjectId, Flags>,
@@ -391,13 +389,13 @@ pub(crate) mod function { · 6 unmodified lines
391 E: std::error::Error + Send + Sync + 'static,389 E: std::error::Error + Send + Sync + 'static,
392 {390 {
393 let mut commits_seen = 0;391 let mut commits_seen = 0;
394 while let Some((commit, _commit_time)) = queue.pop_front() {392 while let Some(commit) = queue.pop() {
395 commits_seen += 1;393 commits_seen += 1;
396 let flags = seen[&commit];394 let flags = seen[&commit];
397 if (flags & best_candidate.identity_bit) == best_candidate.identity_bit {395 if (flags & best_candidate.identity_bit) == best_candidate.identity_bit {
398 if queue396 if queue
399 .iter()397 .iter_random()
400 .all(|(id, _)| (seen[id] & best_candidate.identity_bit) == best_candidate.identity_bit)398 .all(|id| (seen[id] & best_candidate.identity_bit) == best_candidate.identity_bit)
401 {399 {
402 break;400 break;
403 }401 }
@@ -429,3 +427,5 @@ pub(crate) mod function { · 25 unmodified lines
429 order: usize,427 order: usize,
430 }428 }
431}429}
430
431type CommitTime = u32;
gix-revision/src/lib.rs+8−0
@@ -17,3 +17,11 @@ pub mod spec; · 16 unmodified lines
17 17
18mod types;18mod types;
19pub use types::Spec;19pub 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)]
26pub struct PriorityQueue<K: Ord, T>(std::collections::BinaryHeap<queue::Item<K, T>>);
27mod queue;
gix-revision/src/queue.rs+60−0
@@ -0,0 +1,60 @@
1use crate::PriorityQueue;
2use std::cmp::Ordering;
3use std::collections::BinaryHeap;
4
5pub(crate) struct Item<K, T> {
6 key: K,
7 value: T,
8}
9
10impl<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
16impl<K: Ord, T> Eq for Item<K, T> {}
17
18impl<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
24impl<K: Ord, T> Ord for Item<K, T> {
25 fn cmp(&self, other: &Self) -> Ordering {
26 self.key.cmp(&other.key)
27 }
28}
29
30impl<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
52impl<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}