Now it will abort early if there are only hidden tips to be traversed, which cannot change the result anymore.
Unified · Split
| @@ -78,13 +78,13 @@ pub enum Error { · 77 unmodified lines | ||
| 78 | 78 | use Result as Either; |
| 79 | 79 | |
| 80 | 80 | type QueueKey<T> = Either<T, Reverse<T>>; |
| 81 | −type CommitDateQueue = gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, ObjectId>; | |
| 81 | +type CommitDateQueue = gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, (ObjectId, CommitState)>; | |
| 82 | 82 | type Candidates = VecDeque<crate::commit::Info>; |
| 83 | 83 | |
| 84 | 84 | /// The state used and potentially shared by multiple graph traversals. |
| 85 | 85 | #[derive(Clone)] |
| 86 | 86 | pub(super) struct State { |
| 87 | − next: VecDeque<ObjectId>, | |
| 87 | + next: VecDeque<(ObjectId, CommitState)>, | |
| 88 | 88 | queue: CommitDateQueue, |
| 89 | 89 | buf: Vec<u8>, |
| 90 | 90 | seen: gix_revwalk::graph::IdMap<CommitState>, |
| @@ -184,9 +184,10 @@ mod init { · 93 unmodified lines | ||
| 184 | 184 | } |
| 185 | 185 | Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { |
| 186 | 186 | let state = &mut self.state; |
| 187 | − for commit_id in state.next.drain(..) { | |
| 187 | + for (commit_id, commit_state) in state.next.drain(..) { | |
| 188 | 188 | add_to_queue( |
| 189 | 189 | commit_id, |
| 190 | + commit_state, | |
| 190 | 191 | order, |
| 191 | 192 | sorting.cutoff_time(), |
| 192 | 193 | &mut state.queue, |
| @@ -226,11 +227,12 @@ mod init { · 33 unmodified lines | ||
| 226 | 227 | // Assure we *start* traversing hidden variants of a commit first, give them a head-start. |
| 227 | 228 | match self.sorting { |
| 228 | 229 | Sorting::BreadthFirst => { |
| 229 | − state.next.push_front(id_to_ignore); | |
| 230 | + state.next.push_front((id_to_ignore, CommitState::Hidden)); | |
| 230 | 231 | } |
| 231 | 232 | Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { |
| 232 | 233 | add_to_queue( |
| 233 | 234 | id_to_ignore, |
| 235 | + CommitState::Hidden, | |
| 234 | 236 | order, |
| 235 | 237 | self.sorting.cutoff_time(), |
| 236 | 238 | &mut state.queue, |
| @@ -273,6 +275,7 @@ mod init { · 36 unmodified lines | ||
| 273 | 275 | |
| 274 | 276 | fn add_to_queue( |
| 275 | 277 | commit_id: ObjectId, |
| 278 | + commit_state: CommitState, | |
| 276 | 279 | order: CommitTimeOrder, |
| 277 | 280 | cutoff_time: Option<SecondsSinceUnixEpoch>, |
| 278 | 281 | queue: &mut CommitDateQueue, |
| @@ -284,11 +287,11 @@ mod init { · 5 unmodified lines | ||
| 284 | 287 | let key = to_queue_key(time, order); |
| 285 | 288 | match (cutoff_time, order) { |
| 286 | 289 | (Some(cutoff_time), _) if time >= cutoff_time => { |
| 287 | − queue.insert(key, commit_id); | |
| 290 | + queue.insert(key, (commit_id, commit_state)); | |
| 288 | 291 | } |
| 289 | 292 | (Some(_), _) => {} |
| 290 | 293 | (None, _) => { |
| 291 | − queue.insert(key, commit_id); | |
| 294 | + queue.insert(key, (commit_id, commit_state)); | |
| 292 | 295 | } |
| 293 | 296 | } |
| 294 | 297 | Ok(()) |
| @@ -339,10 +342,11 @@ mod init { · 44 unmodified lines | ||
| 339 | 342 | state.clear(); |
| 340 | 343 | state.next.reserve(tips.size_hint().0); |
| 341 | 344 | for tip in tips.map(Into::into) { |
| 342 | − let seen = state.seen.insert(tip, CommitState::Interesting); | |
| 345 | + let commit_state = CommitState::Interesting; | |
| 346 | + let seen = state.seen.insert(tip, commit_state); | |
| 343 | 347 | // We know there can only be duplicate interesting ones. |
| 344 | 348 | if seen.is_none() && predicate(&tip) { |
| 345 | − state.next.push_back(tip); | |
| 349 | + state.next.push_back((tip, commit_state)); | |
| 346 | 350 | } |
| 347 | 351 | } |
| 348 | 352 | } |
| @@ -418,16 +422,23 @@ mod init { · 69 unmodified lines | ||
| 418 | 422 | cutoff: Option<SecondsSinceUnixEpoch>, |
| 419 | 423 | ) -> Option<Result<Info, Error>> { |
| 420 | 424 | let state = &mut self.state; |
| 425 | + let next = &mut state.queue; | |
| 421 | 426 | |
| 422 | 427 | 'skip_hidden: loop { |
| 423 | − let (commit_time, oid) = match state.queue.pop()? { | |
| 428 | + let (commit_time, (oid, _queued_commit_state)) = match next.pop()? { | |
| 424 | 429 | (Newest(t) | Oldest(Reverse(t)), o) => (t, o), |
| 425 | 430 | }; |
| 426 | 431 | let mut parents: ParentIds = Default::default(); |
| 427 | − // TODO(perf): can avoid this lookup by storing state on `queue` respectively. | |
| 428 | − // ALSO: need to look ahead for early aborts, i.e. if there is only hidden left to traverse. | |
| 429 | − // Maybe this can be counted? | |
| 432 | + | |
| 433 | + // Always use the state that is actually stored, as we may change the type as we go. | |
| 430 | 434 | let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); |
| 435 | + if can_deplete_candidates_early( | |
| 436 | + next.iter_unordered().map(|t| t.1), | |
| 437 | + commit_state, | |
| 438 | + state.candidates.as_ref(), | |
| 439 | + ) { | |
| 440 | + return None; | |
| 441 | + } | |
| 431 | 442 | match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { |
| 432 | 443 | Ok(Either::CachedCommit(commit)) => { |
| 433 | 444 | if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { |
| @@ -443,7 +454,7 @@ mod init { · 9 unmodified lines | ||
| 443 | 454 | &mut state.candidates, |
| 444 | 455 | commit_state, |
| 445 | 456 | &mut self.predicate, |
| 446 | − &mut state.queue, | |
| 457 | + next, | |
| 447 | 458 | order, |
| 448 | 459 | cutoff, |
| 449 | 460 | || parent_commit_time, |
| @@ -462,7 +473,7 @@ mod init { · 12 unmodified lines | ||
| 462 | 473 | &mut state.candidates, |
| 463 | 474 | commit_state, |
| 464 | 475 | &mut self.predicate, |
| 465 | − &mut state.queue, | |
| 476 | + next, | |
| 466 | 477 | order, |
| 467 | 478 | cutoff, |
| 468 | 479 | || { |
| @@ -505,6 +516,28 @@ mod init { · 36 unmodified lines | ||
| 505 | 516 | } |
| 506 | 517 | } |
| 507 | 518 | |
| 519 | + /// Returns `true` if we have only hidden cursors queued for traversal, assuming that we don't see interesting ones ever again. | |
| 520 | + /// | |
| 521 | + /// `unqueued_commit_state` is the state of the commit that is currently being processed. | |
| 522 | + fn can_deplete_candidates_early( | |
| 523 | + mut queued_states: impl Iterator<Item = CommitState>, | |
| 524 | + unqueued_commit_state: CommitState, | |
| 525 | + candidates: Option<&Candidates>, | |
| 526 | + ) -> bool { | |
| 527 | + if candidates.is_none() { | |
| 528 | + return false; | |
| 529 | + } | |
| 530 | + if unqueued_commit_state.is_interesting() { | |
| 531 | + return false; | |
| 532 | + } | |
| 533 | + | |
| 534 | + let mut is_empty = true; | |
| 535 | + queued_states.all(|state| { | |
| 536 | + is_empty = false; | |
| 537 | + state.is_hidden() | |
| 538 | + }) && !is_empty | |
| 539 | + } | |
| 540 | + | |
| 508 | 541 | /// Utilities |
| 509 | 542 | impl<Find, Predicate> Simple<Find, Predicate> |
| 510 | 543 | where |
| @@ -513,14 +546,16 @@ mod init { · 2 unmodified lines | ||
| 513 | 546 | { |
| 514 | 547 | fn next_by_topology(&mut self) -> Option<Result<Info, Error>> { |
| 515 | 548 | let state = &mut self.state; |
| 549 | + let next = &mut state.next; | |
| 516 | 550 | 'skip_hidden: loop { |
| 517 | − let oid = state.next.pop_front()?; | |
| 551 | + let (oid, _queued_commit_state) = next.pop_front()?; | |
| 518 | 552 | let mut parents: ParentIds = Default::default(); |
| 519 | − // TODO(perf): can avoid this lookup by storing state on `next` respectively. | |
| 520 | − // ALSO: need to look ahead for early aborts, i.e. if there is only hidden left to traverse. | |
| 521 | − // Maybe this can be counted? | |
| 522 | − let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); | |
| 523 | 553 | |
| 554 | + // Always use the state that is actually stored, as we may change the type as we go. | |
| 555 | + let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); | |
| 556 | + if can_deplete_candidates_early(next.iter().map(|t| t.1), commit_state, state.candidates.as_ref()) { | |
| 557 | + return None; | |
| 558 | + } | |
| 524 | 559 | match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { |
| 525 | 560 | Ok(Either::CachedCommit(commit)) => { |
| 526 | 561 | if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { |
| @@ -537,7 +572,7 @@ mod init { · 10 unmodified lines | ||
| 537 | 572 | &mut state.candidates, |
| 538 | 573 | commit_state, |
| 539 | 574 | &mut self.predicate, |
| 540 | − &mut state.next, | |
| 575 | + next, | |
| 541 | 576 | ); |
| 542 | 577 | if commit_state.is_interesting() && matches!(self.parents, Parents::First) { |
| 543 | 578 | break; |
| @@ -556,7 +591,7 @@ mod init { · 12 unmodified lines | ||
| 556 | 591 | &mut state.candidates, |
| 557 | 592 | commit_state, |
| 558 | 593 | &mut self.predicate, |
| 559 | − &mut state.next, | |
| 594 | + next, | |
| 560 | 595 | ); |
| 561 | 596 | if commit_state.is_interesting() && matches!(self.parents, Parents::First) { |
| 562 | 597 | break; |
| @@ -608,7 +643,7 @@ mod init { · 45 unmodified lines | ||
| 608 | 643 | candidates: &mut Option<Candidates>, |
| 609 | 644 | commit_state: CommitState, |
| 610 | 645 | predicate: &mut impl FnMut(&oid) -> bool, |
| 611 | − next: &mut VecDeque<ObjectId>, | |
| 646 | + next: &mut VecDeque<(ObjectId, CommitState)>, | |
| 612 | 647 | ) { |
| 613 | 648 | let enqueue = match seen.entry(parent_id) { |
| 614 | 649 | Entry::Occupied(mut e) => { |
| @@ -627,7 +662,7 @@ mod init { · 12 unmodified lines | ||
| 627 | 662 | } |
| 628 | 663 | }; |
| 629 | 664 | if enqueue { |
| 630 | − next.push_back(parent_id); | |
| 665 | + next.push_back((parent_id, commit_state)); | |
| 631 | 666 | } |
| 632 | 667 | } |
| 633 | 668 | |
| @@ -665,7 +700,7 @@ mod init { · 31 unmodified lines | ||
| 665 | 700 | let key = to_queue_key(parent_commit_time, order); |
| 666 | 701 | match cutoff { |
| 667 | 702 | Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => {} |
| 668 | − Some(_) | None => queue.insert(key, parent_id), | |
| 703 | + Some(_) | None => queue.insert(key, (parent_id, commit_state)), | |
| 669 | 704 | } |
| 670 | 705 | } |
| 671 | 706 | } |
| @@ -133,9 +133,9 @@ mod hide { · 132 unmodified lines | ||
| 133 | 133 | |
| 134 | 134 | fn all_sortings() -> impl Iterator<Item = Sorting> { |
| 135 | 135 | [ |
| 136 | + Sorting::BreadthFirst, | |
| 136 | 137 | Sorting::ByCommitTime(CommitTimeOrder::NewestFirst), |
| 137 | 138 | Sorting::ByCommitTime(CommitTimeOrder::OldestFirst), |
| 138 | − Sorting::BreadthFirst, | |
| 139 | 139 | ] |
| 140 | 140 | .into_iter() |
| 141 | 141 | } |