pseudo-merge: implement support for finding existing merges
This patch implements support for reusing existing pseudo-merge commits
when writing bitmaps when there is an existing pseudo-merge bitmap which
has exactly the same set of parents as one that we are about to write.
Note that unstable pseudo-merges are likely to change between
consecutive repacks, and so are generally poor candidates for reuse.
However, stable pseudo-merges (see the configuration option
'bitmapPseudoMerge.<name>.stableThreshold') are by definition unlikely
to change between runs (as they represent long-running branches).
Because there is no index from a *set* of pseudo-merge parents to a
matching pseudo-merge bitmap, we have to construct the bitmap
corresponding to the set of parents for each pending pseudo-merge commit
and see if a matching bitmap exists.
This is technically quadratic in the number of pseudo-merges, but is OK
in practice for a couple of reasons:
- non-matching pseudo-merge bitmaps are rejected quickly as soon as
they differ in a single bit
- already-matched pseudo-merge bitmaps are discarded from subsequent
rounds of search
- the number of pseudo-merges is generally small, even for large
repositories
In order to do this, implement (a) a function that finds a matching
pseudo-merge given some uncompressed bitset describing its parents, (b)
a function that computes the bitset of parents for a given pseudo-merge
commit, and (c) call that function before computing the set of reachable
objects for some pending pseudo-merge.
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Taylor Blau committedMay 23, 2024 at 17:27 UTC7252d9a036fabb10f60dc09937fc39e252f3c4d4
6 files changed+174-2
pack-bitmap-write.c
+13-2
index 47250398aa..6e8060f8a0 100644--- a/pack-bitmap-write.c+++ b/pack-bitmap-write.c@@ -19,6 +19,10 @@ #include "tree-walk.h" #include "pseudo-merge.h" #include "oid-array.h"+#include "config.h"+#include "alloc.h"+#include "refs.h"+#include "strmap.h" struct bitmapped_commit { struct commit *commit;@@ -465,6 +469,7 @@ static int fill_bitmap_tree(struct bitmap_writer *writer, } static int reused_bitmaps_nr;+static int reused_pseudo_merge_bitmaps_nr; static int fill_bitmap_commit(struct bitmap_writer *writer, struct bb_commit *ent,@@ -490,7 +495,7 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, struct bitmap *remapped = bitmap_new(); if (commit->object.flags & BITMAP_PSEUDO_MERGE)- old = NULL;+ old = pseudo_merge_bitmap_for_commit(old_bitmap, c); else old = bitmap_for_commit(old_bitmap, c); /*@@ -501,7 +506,10 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, if (old && !rebuild_bitmap(mapping, old, remapped)) { bitmap_or(ent->bitmap, remapped); bitmap_free(remapped);- reused_bitmaps_nr++;+ if (commit->object.flags & BITMAP_PSEUDO_MERGE)+ reused_pseudo_merge_bitmaps_nr++;+ else+ reused_bitmaps_nr++; continue; } bitmap_free(remapped);@@ -631,6 +639,9 @@ int bitmap_writer_build(struct bitmap_writer *writer, the_repository); trace2_data_intmax("pack-bitmap-write", the_repository, "building_bitmaps_reused", reused_bitmaps_nr);+ trace2_data_intmax("pack-bitmap-write", the_repository,+ "building_bitmaps_pseudo_merge_reused",+ reused_pseudo_merge_bitmaps_nr); stop_progress(&writer->progress);
index 7d13101149..a117520996 100644--- a/pseudo-merge.c+++ b/pseudo-merge.c@@ -699,3 +699,58 @@ int cascade_pseudo_merges(const struct pseudo_merge_map *pm, return ret; }++struct pseudo_merge *pseudo_merge_for_parents(const struct pseudo_merge_map *pm,+ struct bitmap *parents)+{+ struct pseudo_merge *match = NULL;+ size_t i;++ if (!pm->nr)+ return NULL;++ /*+ * NOTE: this loop is quadratic in the worst-case (where no+ * matching pseudo-merge bitmaps are found), but in practice+ * this is OK for a few reasons:+ *+ * - Rejecting pseudo-merge bitmaps that do not match the+ * given commit is done quickly (i.e. `bitmap_equals_ewah()`+ * returns early when we know the two bitmaps aren't equal.+ *+ * - Already matched pseudo-merge bitmaps (which we track with+ * the `->satisfied` bit here) are skipped as potential+ * candidates.+ *+ * - The number of pseudo-merges should be small (in the+ * hundreds for most repositories).+ *+ * If in the future this semi-quadratic behavior does become a+ * problem, another approach would be to keep track of which+ * pseudo-merges are still "viable" after enumerating the+ * pseudo-merge commit's parents:+ *+ * - A pseudo-merge bitmap becomes non-viable when the bit(s)+ * corresponding to one or more parent(s) of the given+ * commit are not set in a candidate pseudo-merge's commits+ * bitmap.+ *+ * - After processing all bits, enumerate the remaining set of+ * viable pseudo-merge bitmaps, and check that their+ * popcount() matches the number of parents in the given+ * commit.+ */+ for (i = 0; i < pm->nr; i++) {+ struct pseudo_merge *candidate = use_pseudo_merge(pm, &pm->v[i]);+ if (!candidate || candidate->satisfied)+ continue;+ if (!bitmap_equals_ewah(parents, candidate->commits))+ continue;++ match = candidate;+ match->satisfied = 1;+ break;+ }++ return match;+}
pseudo-merge.h
+7
index 755edc054a..2aca01d056 100644--- a/pseudo-merge.h+++ b/pseudo-merge.h@@ -206,4 +206,11 @@ int cascade_pseudo_merges(const struct pseudo_merge_map *pm, struct bitmap *result, struct bitmap *roots);+/*+ * Returns a pseudo-merge which contains the exact set of commits+ * listed in the "parents" bitamp, or NULL if none could be found.+ */+struct pseudo_merge *pseudo_merge_for_parents(const struct pseudo_merge_map *pm,+ struct bitmap *parents);+ #endif