]> git.ipfire.org Git - thirdparty/git.git/commitdiff
cat-file: only use bitmaps when filtering
authorJeff King <peff@peff.net>
Tue, 6 Jan 2026 10:25:58 +0000 (05:25 -0500)
committerJunio C Hamano <gitster@pobox.com>
Wed, 7 Jan 2026 00:05:12 +0000 (09:05 +0900)
Commit 8002e8ee18 (builtin/cat-file: use bitmaps to efficiently filter
by object type, 2025-04-02) introduced a performance regression when we
are not filtering objects: it uses bitmaps even when they won't help,
incurring extra costs. For example, running the new perf tests from this
commit, which check the performance of listing objects by oid:

  $ export GIT_PERF_LARGE_REPO=/path/to/linux.git
  $ git -C "$GIT_PERF_LARGE_REPO" repack -adb
  $ GIT_SKIP_TESTS=p1006.1 ./run 8002e8ee188002e8ee18 p1006-cat-file.sh
  [...]
  Test                                  8002e8ee18^       8002e8ee18
  -------------------------------------------------------------------------------
  1006.2: list all objects (sorted)     1.48(1.44+0.04)   6.39(6.35+0.04) +331.8%
  1006.3: list all objects (unsorted)   3.01(2.97+0.04)   3.40(3.29+0.10) +13.0%
  1006.4: list blobs                    4.85(4.67+0.17)   1.68(1.58+0.10) -65.4%

An invocation that filters, like listing all blobs (1006.4), does
benefit from using the bitmaps; it now doesn't have to check the type of
each object from the pack data, so the tradeoff is worth it.

But for listing all objects in sorted idx order (1006.2), we otherwise
would never open the bitmap nor the revindex file. Worse, our sorting
step gets much worse. Normally we append into an array in pack .idx
order, and the sort step is trivial. But with bitmaps, we get the
objects in pack order, which is apparently random with respect to oid,
and have to sort the whole thing. (Note that this freshly-packed state
represents the best case for .idx sorting; if we had two packs, then
we'd have their objects one after the other and qsort would have to
interleave them).

The unsorted test in 1006.3 is interesting: there we are going in pack
order, so we load the revindex for the pack anyway. And though we don't
sort the result, we do use an oidset to check for duplicates. So we can
see in the 8002e8ee18^ timings that those two things cost ~1.5s over the
sorted case (mostly the oidset hash cost). We also incur the extra cost
to open the bitmap file as of 8002e8ee18, which seems to be ~400ms.
(This would probably be faster with a bitmap lookup table, but writing
that out is not yet the default).

So we know that bitmaps help when there's filtering to be done, but
otherwise make things worse. Let's only use them when there's a filter.

The perf script shows that we've fixed the regressions without hurting
the bitmap case:

  Test                                  8002e8ee18^       8002e8ee18                HEAD
  --------------------------------------------------------------------------------------------------------
  1006.2: list all objects (sorted)     1.56(1.53+0.03)   6.44(6.37+0.06) +312.8%   1.62(1.54+0.06) +3.8%
  1006.3: list all objects (unsorted)   3.04(2.98+0.06)   3.45(3.38+0.07) +13.5%    3.04(2.99+0.04) +0.0%
  1006.4: list blobs                    5.14(4.98+0.15)   1.76(1.68+0.06) -65.8%    1.73(1.64+0.09) -66.3%

Note that there's another related case: we might have a filter that
cannot be used with bitmaps. That check is handled already for us in
for_each_bitmapped_object(), though we'd still load the bitmap and
revindex files pointlessly in that case. I don't think it can happen in
practice for cat-file, though, since it allows only blob:none,
blob:limit, and object:type filters, all of which work with bitmaps.

It would be easy-ish to insert an extra check like:

  can_filter_bitmap(&opt->objects_filter);

into the conditional, but I didn't bother here. It would be redundant
with the call in for_each_bitmapped_object(), and the can_filter helper
function is static local in the bitmap code (so we'd have to make it
public).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/cat-file.c
t/perf/p1006-cat-file.sh

index 67a5ff2b9ebd29d7d2bad988c44abc885dd9f44c..6d704ec6c9839862452e3d82d94afc97c62aef4b 100644 (file)
@@ -839,12 +839,14 @@ static void batch_each_object(struct batch_options *opt,
                .callback = callback,
                .payload = _payload,
        };
-       struct bitmap_index *bitmap = prepare_bitmap_git(the_repository);
+       struct bitmap_index *bitmap = NULL;
 
        for_each_loose_object(batch_one_object_loose, &payload, 0);
 
-       if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,
-                                                batch_one_object_bitmapped, &payload)) {
+       if (opt->objects_filter.choice != LOFC_DISABLED &&
+           (bitmap = prepare_bitmap_git(the_repository)) &&
+           !for_each_bitmapped_object(bitmap, &opt->objects_filter,
+                                      batch_one_object_bitmapped, &payload)) {
                struct packed_git *pack;
 
                for (pack = get_all_packs(the_repository); pack; pack = pack->next) {
index dcd801537968b3d325e3dd6b710ab829acf6d42a..da34ece2428a4dc8fcfe5f31505920a8125bb880 100755 (executable)
@@ -9,4 +9,18 @@ test_perf 'cat-file --batch-check' '
        git cat-file --batch-all-objects --batch-check
 '
 
+test_perf 'list all objects (sorted)' '
+       git cat-file --batch-all-objects --batch-check="%(objectname)"
+'
+
+test_perf 'list all objects (unsorted)' '
+       git cat-file --batch-all-objects --batch-check="%(objectname)" \
+               --unordered
+'
+
+test_perf 'list blobs' '
+       git cat-file --batch-all-objects --batch-check="%(objectname)" \
+               --unordered --filter=object:type=blob
+'
+
 test_done