git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 9/9] builtin/cat-file: use bitmaps to efficiently filter by object type

From
Patrick Steinhardt <ps@pks.im>
Date
Feb 21, 2025, 07:47 UTC
Message-ID
<20250221-pks-cat-file-object-type-filter-v1-9-0852530888e2@pks.im>
In-Reply-To
<20250221-pks-cat-file-object-type-filter-v1-0-0852530888e2@pks.im>

While it is now possible to filter objects by type, this mechanism is for now mostly a convenience. Most importantly, we still have to iterate through the whole packfile to find all objects of a specific type. This can be prohibitively expensive depending on the size of the packfiles.

It isn't really possible to do better than this when only considering a packfile itself, as the order of objects is not fixed. But when we have a packfile with a corresponding bitmap, either because the packfile itself has one or because the multi-pack index has a bitmap for it, then we can use these bitmaps to improve the runtime.

While bitmaps are typically used to compute reachability of objects, they also contain one bitmap per object type encodes which object has what type. So instead of reading through the whole packfile(s), we can use the bitmaps and iterate through the type-specific bitmap. Typically, only a subset of packfiles will have a bitmap. But this isn't really much of a problem: we can use bitmaps when available, and then use the non-bitmap walk for every packfile that isn't covered by one.

Overall, this leads to quite a significant speedup depending on how many objects of a certain type exist. The following benchmarks have been executed in the Chromium repository, which has a 50GB packfile with almost 25 million objects:

    Benchmark 1: git cat-file --batch-check --batch-all-objects --unordered --buffer --no-objects-filter
      Time (mean ± σ):     82.806 s ±  6.363 s    [User: 30.956 s, System: 8.264 s]
      Range (min … max):   73.936 s … 89.690 s    10 runs
    Benchmark 2: git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=tag
      Time (mean ± σ):      20.8 ms ±   1.3 ms    [User: 6.1 ms, System: 14.5 ms]
      Range (min … max):    18.2 ms …  23.6 ms    127 runs
    Benchmark 3: git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=commit
      Time (mean ± σ):      1.551 s ±  0.008 s    [User: 1.401 s, System: 0.147 s]
      Range (min … max):    1.541 s …  1.566 s    10 runs
    Benchmark 4: git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=tree
      Time (mean ± σ):     11.169 s ±  0.046 s    [User: 10.076 s, System: 1.063 s]
      Range (min … max):   11.114 s … 11.245 s    10 runs
    Benchmark 5: git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=blob
      Time (mean ± σ):     67.342 s ±  3.368 s    [User: 20.318 s, System: 7.787 s]
      Range (min … max):   62.836 s … 73.618 s    10 runs
    Benchmark 6: git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=blob:none
      Time (mean ± σ):     13.032 s ±  0.072 s    [User: 11.638 s, System: 1.368 s]
      Range (min … max):   12.960 s … 13.199 s    10 runs
    Summary
      git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=tag
       74.75 ± 4.61 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=commit
      538.17 ± 33.17 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=tree
      627.98 ± 38.77 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=blob:none
     3244.93 ± 257.23 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --objects-filter=object:type=blob
     3990.07 ± 392.72 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --no-objects-filter

The first benchmark is mostly equivalent in runtime compared to all the others without the bitmap-optimization introduced in this commit. What is noticeable in the benchmarks is that we're I/O-bound, not CPU-bound, as can be seen from the user/system runtimes, which is often way lower than the overall benchmarked runtime.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/cat-file.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 50 insertions(+), 5 deletions(-)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 25d5429e391..9021fd52f30 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -21,6 +21,7 @@
 #include "streaming.h"
 #include "oid-array.h"
 #include "packfile.h"
+#include "pack-bitmap.h"
 #include "object-file.h"
 #include "object-name.h"
 #include "object-store-ll.h"
@@ -805,7 +806,20 @@ static int batch_one_object_packed(const struct object_id *oid,
 				 payload->payload);
 }
 
-static void batch_each_object(for_each_object_fn callback,
+static int batch_one_object_bitmapped(const struct object_id *oid,
+				      enum object_type type UNUSED,
+				      int flags UNUSED,
+				      uint32_t hash UNUSED,
+				      struct packed_git *pack,
+				      off_t offset,
+				      void *_payload)
+{
+	struct for_each_object_payload *payload = _payload;
+	return payload->callback(oid, pack, offset, payload->payload);
+}
+
+static void batch_each_object(struct batch_options *opt,
+			      for_each_object_fn callback,
 			      unsigned flags,
 			      void *_payload)
 {
@@ -813,9 +827,40 @@ static void batch_each_object(for_each_object_fn callback,
 		.callback = callback,
 		.payload = _payload,
 	};
+	struct bitmap_index *bitmap = prepare_bitmap_git(the_repository);
+
 	for_each_loose_object(batch_one_object_loose, &payload, 0);
-	for_each_packed_object(the_repository, batch_one_object_packed,
-			       &payload, flags);
+
+	if (bitmap &&
+	    (opt->objects_filter.choice == LOFC_OBJECT_TYPE ||
+	     opt->objects_filter.choice == LOFC_BLOB_NONE)) {
+		struct packed_git *pack;
+
+		if (opt->objects_filter.choice == LOFC_OBJECT_TYPE) {
+			for_each_bitmapped_object(bitmap, opt->objects_filter.object_type,
+						  batch_one_object_bitmapped, &payload);
+		} else {
+			for_each_bitmapped_object(bitmap, OBJ_COMMIT,
+						  batch_one_object_bitmapped, &payload);
+			for_each_bitmapped_object(bitmap, OBJ_TAG,
+						  batch_one_object_bitmapped, &payload);
+			for_each_bitmapped_object(bitmap, OBJ_TREE,
+						  batch_one_object_bitmapped, &payload);
+		}
+
+		for (pack = get_all_packs(the_repository); pack; pack = pack->next) {
+			if (bitmap_index_contains_pack(bitmap, pack) ||
+			    open_pack_index(pack))
+				continue;
+			for_each_object_in_pack(pack, batch_one_object_packed,
+						&payload, flags);
+		}
+	} else {
+		for_each_packed_object(the_repository, batch_one_object_packed,
+				       &payload, flags);
+	}
+
+	free_bitmap_index(bitmap);
 }
 
 static int batch_objects(struct batch_options *opt)
@@ -872,14 +917,14 @@ static int batch_objects(struct batch_options *opt)
 
 			cb.seen = &seen;
 
-			batch_each_object(batch_unordered_object,
+			batch_each_object(opt, batch_unordered_object,
 					  FOR_EACH_OBJECT_PACK_ORDER, &cb);
 
 			oidset_clear(&seen);
 		} else {
 			struct oid_array sa = OID_ARRAY_INIT;
 
-			batch_each_object(collect_object, 0, &sa);
+			batch_each_object(opt, collect_object, 0, &sa);
 			oid_array_for_each_unique(&sa, batch_object_cb, &cb);
 
 			oid_array_clear(&sa);
-- 
2.48.1.683.gf705b3209c.dirty
Previous: Taylor BlauNext: Karthik Nayak
Message 28 of 44 in “builtin/cat-file: allow filtering objects in batch mode”
  1. 0/9 builtin/cat-file: allow filtering objects in batch modePatrick Steinhardt, Feb 21, 2025
  2. 1/9 builtin/cat-file: rename variable that tracks usagePatrick Steinhardt, Feb 21, 2025
  3. 3/9 builtin/cat-file: support "blob:none" objects filterPatrick Steinhardt, Feb 21, 2025
  4. Toon ClaesFeb 26, 2025
  5. Karthik NayakFeb 27, 2025
  6. 2/9 builtin/cat-file: wire up an option to filter objectsPatrick Steinhardt, Feb 21, 2025
  7. Toon ClaesFeb 26, 2025
  8. Patrick SteinhardtFeb 28, 2025
  9. Junio C HamanoFeb 28, 2025
  10. Patrick SteinhardtMar 3, 2025
  11. Karthik NayakFeb 27, 2025
  12. 4/9 builtin/cat-file: support "blob:limit=" objects filterPatrick Steinhardt, Feb 21, 2025
  13. 5/9 builtin/cat-file: support "object:type=" objects filterPatrick Steinhardt, Feb 21, 2025
  14. Toon ClaesFeb 26, 2025
  15. Patrick SteinhardtFeb 28, 2025
  16. 6/9 pack-bitmap: expose function to iterate over bitmapped objectsPatrick Steinhardt, Feb 21, 2025
  17. Junio C HamanoFeb 24, 2025
  18. Patrick SteinhardtFeb 25, 2025
  19. Junio C HamanoFeb 25, 2025
  20. Taylor BlauFeb 27, 2025
  21. Patrick SteinhardtFeb 28, 2025
  22. Taylor BlauFeb 27, 2025
  23. Junio C HamanoFeb 27, 2025
  24. Taylor BlauFeb 27, 2025
  25. 8/9 builtin/cat-file: deduplicate logic to iterate over all objectsPatrick Steinhardt, Feb 21, 2025
  26. 7/9 pack-bitmap: introduce function to check whether a pack is bitmappedPatrick Steinhardt, Feb 21, 2025
  27. Taylor BlauFeb 27, 2025
  28. 9/9 builtin/cat-file: use bitmaps to efficiently filter by object typePatrick Steinhardt, Feb 21, 2025
  29. Karthik NayakFeb 27, 2025
  30. Taylor BlauFeb 27, 2025
  31. 00/11 builtin/cat-file: allow filtering objects in batch modePatrick Steinhardt, Apr 2, 2025
  32. 01/11 builtin/cat-file: rename variable that tracks usagePatrick Steinhardt, Apr 2, 2025
  33. 02/11 builtin/cat-file: introduce function to report object statusPatrick Steinhardt, Apr 2, 2025
  34. 03/11 builtin/cat-file: wire up an option to filter objectsPatrick Steinhardt, Apr 2, 2025
  35. 04/11 builtin/cat-file: support "blob:none" objects filterPatrick Steinhardt, Apr 2, 2025
  36. 05/11 builtin/cat-file: support "blob:limit=" objects filterPatrick Steinhardt, Apr 2, 2025
  37. 06/11 builtin/cat-file: support "object:type=" objects filterPatrick Steinhardt, Apr 2, 2025
  38. 07/11 pack-bitmap: allow passing payloads to `show_reachable_fn()`Patrick Steinhardt, Apr 2, 2025
  39. 08/11 pack-bitmap: add function to iterate over filtered bitmapped objectsPatrick Steinhardt, Apr 2, 2025
  40. 09/11 pack-bitmap: introduce function to check whether a pack is bitmappedPatrick Steinhardt, Apr 2, 2025
  41. 10/11 builtin/cat-file: deduplicate logic to iterate over all objectsPatrick Steinhardt, Apr 2, 2025
  42. 11/11 builtin/cat-file: use bitmaps to efficiently filter by object typePatrick Steinhardt, Apr 2, 2025
  43. Karthik NayakApr 3, 2025
  44. Junio C HamanoApr 8, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.