ref-filter: parse objects on demand

When formatting an arbitrary object we parse that object regardless of whether or not we actually need any parsed data. In fact, many of the atoms we have don't require any. Refactor the code so that we parse the data on demand when we see an atom that wants to access the objects. This leads to a small speedup, for example in the Chromium repository with around 40000 refs: Benchmark 1: for-each-ref --format='%(raw)' (HEAD~) Time (mean ± σ): 388.7 ms ± 1.1 ms [User: 322.2 ms, System: 65.0 ms] Range (min … max): 387.3 ms … 390.8 ms 10 runs Benchmark 2: for-each-ref --format='%(raw)' (HEAD) Time (mean ± σ): 344.7 ms ± 0.7 ms [User: 287.8 ms, System: 55.1 ms] Range (min … max): 343.9 ms … 345.7 ms 10 runs Summary for-each-ref --format='%(raw)' (HEAD) ran 1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~) With this change, we now spend ~90% of the time decompressing objects, which is almost as good as it gets regarding git-for-each-ref(1)'s own infrastructure. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Oct 23, 2025 at 09:16 UTC a29e2e8fe7e3935e23d2a03dc429cc9c2e68bfbe
1 file changed +106 -36
ref-filter.c
+106 -36
@@ -91,6 +91,7 @@ static struct expand_data {
91 struct object_id delta_base_oid;
92 void *content;
93
94 + struct object *maybe_object;
95 struct object_info info;
96 } oi, oi_deref;
97
@@ -1475,11 +1476,29 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
1476 }
1477 }
1478
1479 +static struct object *get_or_parse_object(struct expand_data *data, const char *refname,
1480 + struct strbuf *err, int *eaten)
1481 +{
1482 + if (!data->maybe_object) {
1483 + data->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,
1484 + data->size, data->content, eaten);
1485 + if (!data->maybe_object) {
1486 + strbuf_addf(err, _("parse_object_buffer failed on %s for %s"),
1487 + oid_to_hex(&data->oid), refname);
1488 + return NULL;
1489 + }
1490 + }
1491 +
1492 + return data->maybe_object;
1493 +}
1494 +
1495 /* See grab_values */
1479 -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)
1496 +static int grab_tag_values(struct atom_value *val, int deref,
1497 + struct expand_data *data, const char *refname,
1498 + struct strbuf *err, int *eaten)
1499 {
1500 + struct tag *tag = NULL;
1501 int i;
1482 - struct tag *tag = (struct tag *) obj;
1502
1503 for (i = 0; i < used_atom_cnt; i++) {
1504 const char *name = used_atom[i].name;
@@ -1487,6 +1506,14 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
1506 struct atom_value *v = &val[i];
1507 if (!!deref != (*name == '*'))
1508 continue;
1509 +
1510 + if (!tag) {
1511 + tag = (struct tag *) get_or_parse_object(data, refname,
1512 + err, eaten);
1513 + if (!tag)
1514 + return -1;
1515 + }
1516 +
1517 if (deref)
1518 name++;
1519 if (atom_type == ATOM_TAG)
@@ -1496,22 +1523,35 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
1523 else if (atom_type == ATOM_OBJECT && tag->tagged)
1524 v->s = xstrdup(oid_to_hex(&tag->tagged->oid));
1525 }
1526 +
1527 + return 0;
1528 }
1529
1530 /* See grab_values */
1502 -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)
1531 +static int grab_commit_values(struct atom_value *val, int deref,
1532 + struct expand_data *data, const char *refname,
1533 + struct strbuf *err, int *eaten)
1534 {
1535 int i;
1505 - struct commit *commit = (struct commit *) obj;
1536 + struct commit *commit = NULL;
1537
1538 for (i = 0; i < used_atom_cnt; i++) {
1539 const char *name = used_atom[i].name;
1540 enum atom_type atom_type = used_atom[i].atom_type;
1541 struct atom_value *v = &val[i];
1542 +
1543 if (!!deref != (*name == '*'))
1544 continue;
1545 if (deref)
1546 name++;
1547 +
1548 + if (!commit) {
1549 + commit = (struct commit *) get_or_parse_object(data, refname,
1550 + err, eaten);
1551 + if (!commit)
1552 + return -1;
1553 + }
1554 +
1555 if (atom_type == ATOM_TREE &&
1556 grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
1557 continue;
@@ -1531,6 +1571,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
1571 v->s = strbuf_detach(&s, NULL);
1572 }
1573 }
1574 +
1575 + return 0;
1576 }
1577
1578 static const char *find_wholine(const char *who, int wholen, const char *buf)
@@ -1759,10 +1801,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
1801 }
1802 }
1803
1762 -static void grab_signature(struct atom_value *val, int deref, struct object *obj)
1804 +static int grab_signature(struct atom_value *val, int deref,
1805 + struct expand_data *data, const char *refname,
1806 + struct strbuf *err, int *eaten)
1807 {
1808 int i;
1765 - struct commit *commit = (struct commit *) obj;
1809 + struct commit *commit = NULL;
1810 struct signature_check sigc = { 0 };
1811 int signature_checked = 0;
1812
@@ -1790,6 +1834,13 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj
1834 continue;
1835
1836 if (!signature_checked) {
1837 + if (!commit) {
1838 + commit = (struct commit *) get_or_parse_object(data, refname,
1839 + err, eaten);
1840 + if (!commit)
1841 + return -1;
1842 + }
1843 +
1844 check_commit_signature(commit, &sigc);
1845 signature_checked = 1;
1846 }
@@ -1843,6 +1894,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj
1894
1895 if (signature_checked)
1896 signature_check_clear(&sigc);
1897 +
1898 + return 0;
1899 }
1900
1901 static void find_subpos(const char *buf,
@@ -1920,9 +1973,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
1973 }
1974
1975 static void grab_describe_values(struct atom_value *val, int deref,
1923 - struct object *obj)
1976 + struct expand_data *data)
1977 {
1925 - struct commit *commit = (struct commit *)obj;
1978 int i;
1979
1980 for (i = 0; i < used_atom_cnt; i++) {
@@ -1944,7 +1996,7 @@ static void grab_describe_values(struct atom_value *val, int deref,
1996 cmd.git_cmd = 1;
1997 strvec_push(&cmd.args, "describe");
1998 strvec_pushv(&cmd.args, atom->u.describe_args.v);
1947 - strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
1999 + strvec_push(&cmd.args, oid_to_hex(&data->oid));
2000 if (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {
2001 error(_("failed to run 'describe'"));
2002 v->s = xstrdup("");
@@ -2066,24 +2118,36 @@ static void fill_missing_values(struct atom_value *val)
2118 * pointed at by the ref itself; otherwise it is the object the
2119 * ref (which is a tag) refers to.
2120 */
2069 -static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)
2121 +static int grab_values(struct atom_value *val, int deref, struct expand_data *data,
2122 + const char *refname, struct strbuf *err, int *eaten)
2123 {
2124 void *buf = data->content;
2125 + int ret;
2126
2073 - switch (obj->type) {
2127 + switch (data->type) {
2128 case OBJ_TAG:
2075 - grab_tag_values(val, deref, obj);
2129 + ret = grab_tag_values(val, deref, data, refname, err, eaten);
2130 + if (ret < 0)
2131 + goto out;
2132 +
2133 grab_sub_body_contents(val, deref, data);
2134 grab_person("tagger", val, deref, buf);
2078 - grab_describe_values(val, deref, obj);
2135 + grab_describe_values(val, deref, data);
2136 break;
2137 case OBJ_COMMIT:
2081 - grab_commit_values(val, deref, obj);
2138 + ret = grab_commit_values(val, deref, data, refname, err, eaten);
2139 + if (ret < 0)
2140 + goto out;
2141 +
2142 grab_sub_body_contents(val, deref, data);
2143 grab_person("author", val, deref, buf);
2144 grab_person("committer", val, deref, buf);
2085 - grab_signature(val, deref, obj);
2086 - grab_describe_values(val, deref, obj);
2145 +
2146 + ret = grab_signature(val, deref, data, refname, err, eaten);
2147 + if (ret < 0)
2148 + goto out;
2149 +
2150 + grab_describe_values(val, deref, data);
2151 break;
2152 case OBJ_TREE:
2153 /* grab_tree_values(val, deref, obj, buf, sz); */
@@ -2094,8 +2158,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
2158 grab_sub_body_contents(val, deref, data);
2159 break;
2160 default:
2097 - die("Eh? Object of type %d?", obj->type);
2161 + die("Eh? Object of type %d?", data->type);
2162 }
2163 +
2164 + ret = 0;
2165 +out:
2166 + return ret;
2167 }
2168
2169 static inline char *copy_advance(char *dst, const char *src)
@@ -2292,38 +2360,41 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re
2360 return show_ref(&atom->u.refname, ref->refname);
2361 }
2362
2295 -static int get_object(struct ref_array_item *ref, int deref, struct object **obj,
2363 +static int get_object(struct ref_array_item *ref, int deref,
2364 struct expand_data *oi, struct strbuf *err)
2365 {
2298 - /* parse_object_buffer() will set eaten to 0 if free() will be needed */
2299 - int eaten = 1;
2366 + /* parse_object_buffer() will set eaten to 1 if free() will be needed */
2367 + int eaten = 0;
2368 + int ret;
2369 +
2370 if (oi->info.contentp) {
2371 /* We need to know that to use parse_object_buffer properly */
2372 oi->info.sizep = &oi->size;
2373 oi->info.typep = &oi->type;
2374 }
2375 +
2376 if (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,
2306 - OBJECT_INFO_LOOKUP_REPLACE))
2307 - return strbuf_addf_ret(err, -1, _("missing object %s for %s"),
2308 - oid_to_hex(&oi->oid), ref->refname);
2377 + OBJECT_INFO_LOOKUP_REPLACE)) {
2378 + ret = strbuf_addf_ret(err, -1, _("missing object %s for %s"),
2379 + oid_to_hex(&oi->oid), ref->refname);
2380 + goto out;
2381 + }
2382 if (oi->info.disk_sizep && oi->disk_size < 0)
2383 BUG("Object size is less than zero.");
2384
2385 if (oi->info.contentp) {
2313 - *obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);
2314 - if (!*obj) {
2315 - if (!eaten)
2316 - free(oi->content);
2317 - return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
2318 - oid_to_hex(&oi->oid), ref->refname);
2319 - }
2320 - grab_values(ref->value, deref, *obj, oi);
2386 + ret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);
2387 + if (ret < 0)
2388 + goto out;
2389 }
2390
2391 grab_common_values(ref->value, deref, oi);
2392 + ret = 0;
2393 +
2394 +out:
2395 if (!eaten)
2396 free(oi->content);
2326 - return 0;
2397 + return ret;
2398 }
2399
2400 static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)
@@ -2376,7 +2447,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)
2447 */
2448 static int populate_value(struct ref_array_item *ref, struct strbuf *err)
2449 {
2379 - struct object *obj;
2450 int i;
2451 struct object_info empty = OBJECT_INFO_INIT;
2452 int ahead_behind_atoms = 0;
@@ -2564,14 +2634,14 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
2634
2635
2636 oi.oid = ref->objectname;
2567 - if (get_object(ref, 0, &obj, &oi, err))
2637 + if (get_object(ref, 0, &oi, err))
2638 return -1;
2639
2640 /*
2641 * If there is no atom that wants to know about tagged
2642 * object, we are done.
2643 */
2574 - if (!need_tagged || (obj->type != OBJ_TAG))
2644 + if (!need_tagged || (oi.type != OBJ_TAG))
2645 return 0;
2646
2647 /*
@@ -2589,7 +2659,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
2659 }
2660 }
2661
2592 - return get_object(ref, 1, &obj, &oi_deref, err);
2662 + return get_object(ref, 1, &oi_deref, err);
2663 }
2664
2665 /*