Skip to content

Commit 2d3b147

Browse files
author
Arijit Banerjee
committed
index-pack: speed up promisor link recording
When indexing a promisor pack, index-pack parses every reconstructed non-blob object into the shared object model to record its outgoing links. Since parse_object_buffer() runs under read_mutex, worker threads serialize while allocating persistent tree, commit, and tag structures that are only needed to enumerate those links. Read the links directly from the reconstructed object buffers instead. Keep the strict and fsck paths unchanged, use worker-local typed oidmaps during normal promisor indexing, and merge them after the workers exit. Transfer entries during the merge so that it does not temporarily duplicate the complete link set. The typed entries preserve checks previously performed as a side effect of object parsing. Reject malformed commit and tag headers, conflicting expected types, and targets whose actual type disagrees when the target is present in the pack. Preserve commit-graft handling and the existing policy of recording only subtree entries from trees. With three runs per version on Debian 12, median end-to-end wall-clock time for a --filter=blob:none clone of linux.git decreased from 156 seconds to 133 seconds (15%). Trace2 attributed the change to the initial index-pack --promisor phase, whose median duration decreased from 121 seconds to 98 seconds (19%). System CPU time decreased by 46%. Two paired spot checks against GitHub showed end-to-end reductions of 18% and 26%. These measurements include network and server variability and are therefore corroborating rather than controlled results. A third pair was not interpretable because the baseline request encountered a transport stall. A full-clone control showed no material change, taking approximately 256 seconds with either version. This is expected because full clones do not exercise promisor-link recording. t5302-pack-index.sh passed with both SHA-1 and SHA-256, while t0410-partial-clone.sh and t5616-partial-clone.sh also passed. New coverage checks malformed commit headers, conflicting link types, and mismatched tag target types. Signed-off-by: Arijit Banerjee <arijit@effectiveailabs.com>
1 parent a97fcc3 commit 2d3b147

2 files changed

Lines changed: 265 additions & 19 deletions

File tree

builtin/index-pack.c

Lines changed: 215 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
#include "odb.h"
2424
#include "odb/streaming.h"
2525
#include "oid-array.h"
26-
#include "oidset.h"
26+
#include "hash-lookup.h"
27+
#include "oidmap.h"
2728
#include "path.h"
2829
#include "replace-object.h"
2930
#include "tree-walk.h"
@@ -105,6 +106,12 @@ static size_t base_cache_limit;
105106
struct thread_local_data {
106107
pthread_t thread;
107108
int pack_fd;
109+
struct oidmap outgoing_links;
110+
};
111+
112+
struct outgoing_link {
113+
struct oidmap_entry entry;
114+
enum object_type type;
108115
};
109116

110117
/* Remember to update object flag allocation in object.h */
@@ -155,11 +162,8 @@ static uint32_t input_crc32;
155162
static int input_fd, output_fd;
156163
static const char *curr_pack;
157164

158-
/*
159-
* outgoing_links is guarded by read_mutex, and record_outgoing_links is
160-
* read-only in a thread.
161-
*/
162-
static struct oidset outgoing_links = OIDSET_INIT;
165+
/* Worker-local maps are merged after all workers have exited. */
166+
static struct oidmap outgoing_links = OIDMAP_INIT;
163167
static int record_outgoing_links;
164168

165169
static struct thread_local_data *thread_data;
@@ -196,6 +200,55 @@ static inline void unlock_mutex(pthread_mutex_t *mutex)
196200
pthread_mutex_unlock(mutex);
197201
}
198202

203+
static void record_outgoing_link_to(struct oidmap *map,
204+
const struct object_id *oid,
205+
enum object_type type)
206+
{
207+
struct outgoing_link *link = oidmap_get(map, oid);
208+
209+
if (link) {
210+
if (type != OBJ_ANY && link->type != OBJ_ANY &&
211+
type != link->type)
212+
die(_("object %s is referred to as both a %s and a %s"),
213+
oid_to_hex(oid), type_name(link->type),
214+
type_name(type));
215+
if (link->type == OBJ_ANY)
216+
link->type = type;
217+
return;
218+
}
219+
220+
CALLOC_ARRAY(link, 1);
221+
oidcpy(&link->entry.oid, oid);
222+
link->type = type;
223+
if (oidmap_put(map, link))
224+
BUG("duplicate outgoing link");
225+
}
226+
227+
static void merge_outgoing_links(struct oidmap *dest, struct oidmap *src)
228+
{
229+
struct oidmap_iter iter;
230+
struct outgoing_link *link;
231+
232+
while ((link = oidmap_iter_first(src, &iter))) {
233+
struct outgoing_link *old = oidmap_get(dest, &link->entry.oid);
234+
235+
oidmap_remove(src, &link->entry.oid);
236+
if (old) {
237+
if (link->type != OBJ_ANY && old->type != OBJ_ANY &&
238+
link->type != old->type)
239+
die(_("object %s is referred to as both a %s and a %s"),
240+
oid_to_hex(&link->entry.oid),
241+
type_name(old->type), type_name(link->type));
242+
if (old->type == OBJ_ANY)
243+
old->type = link->type;
244+
free(link);
245+
} else if (oidmap_put(dest, link)) {
246+
BUG("duplicate outgoing link");
247+
}
248+
}
249+
oidmap_clear(src, 0);
250+
}
251+
199252
/*
200253
* Mutex and conditional variable can't be statically-initialized on Windows.
201254
*/
@@ -211,6 +264,7 @@ static void init_thread(void)
211264
CALLOC_ARRAY(thread_data, nr_threads);
212265
for (i = 0; i < nr_threads; i++) {
213266
thread_data[i].pack_fd = xopen(curr_pack, O_RDONLY);
267+
oidmap_init(&thread_data[i].outgoing_links, 0);
214268
}
215269

216270
threads_active = 1;
@@ -221,14 +275,17 @@ static void cleanup_thread(void)
221275
int i;
222276
if (!threads_active)
223277
return;
224-
threads_active = 0;
225278
pthread_mutex_destroy(&read_mutex);
226279
pthread_mutex_destroy(&counter_mutex);
227280
pthread_mutex_destroy(&work_mutex);
228281
if (show_stat)
229282
pthread_mutex_destroy(&deepest_delta_mutex);
230-
for (i = 0; i < nr_threads; i++)
283+
for (i = 0; i < nr_threads; i++) {
284+
merge_outgoing_links(&outgoing_links,
285+
&thread_data[i].outgoing_links);
231286
close(thread_data[i].pack_fd);
287+
}
288+
threads_active = 0;
232289
pthread_key_delete(key);
233290
free(thread_data);
234291
}
@@ -818,9 +875,14 @@ static int check_collison(struct object_entry *entry)
818875
return 0;
819876
}
820877

821-
static void record_outgoing_link(const struct object_id *oid)
878+
static void record_outgoing_link(const struct object_id *oid,
879+
enum object_type type)
822880
{
823-
oidset_insert(&outgoing_links, oid);
881+
struct oidmap *map = &outgoing_links;
882+
883+
if (threads_active && !strict && !do_fsck_object)
884+
map = &get_thread_data()->outgoing_links;
885+
record_outgoing_link_to(map, oid, type);
824886
}
825887

826888
static void maybe_record_name_entry(const struct name_entry *entry)
@@ -849,7 +911,97 @@ static void maybe_record_name_entry(const struct name_entry *entry)
849911
* pack, so it won't be GC-ed, the tradeoff seems worth it.
850912
*/
851913
if (S_ISDIR(entry->mode))
852-
record_outgoing_link(&entry->oid);
914+
record_outgoing_link(&entry->oid, OBJ_ANY);
915+
}
916+
917+
static int parse_outgoing_link_oid(const char **buf, const char *tail,
918+
const char *header, struct object_id *oid)
919+
{
920+
const char *end;
921+
size_t header_len = strlen(header);
922+
923+
if (tail - *buf <= header_len + the_hash_algo->hexsz ||
924+
memcmp(*buf, header, header_len) ||
925+
parse_oid_hex_algop(*buf + header_len, oid, &end,
926+
the_hash_algo) ||
927+
end >= tail || *end != '\n')
928+
return -1;
929+
*buf = end + 1;
930+
return 0;
931+
}
932+
933+
static void record_outgoing_links_from_data(const void *data,
934+
unsigned long size,
935+
enum object_type type,
936+
const struct object_id *oid)
937+
{
938+
const char *buf = data;
939+
const char *tail = buf + size;
940+
941+
if (type == OBJ_TREE) {
942+
struct tree_desc desc;
943+
struct name_entry entry;
944+
945+
if (init_tree_desc_gently(&desc, oid, data, size, 0))
946+
return;
947+
while (tree_entry_gently(&desc, &entry))
948+
maybe_record_name_entry(&entry);
949+
} else if (type == OBJ_COMMIT) {
950+
struct object_id link;
951+
struct commit_graft *graft;
952+
int i;
953+
954+
if (threads_active &&
955+
!the_repository->parsed_objects->commit_graft_prepared)
956+
BUG("commit grafts were not prepared before resolving deltas");
957+
graft = lookup_commit_graft(the_repository, oid);
958+
959+
if (parse_outgoing_link_oid(&buf, tail, "tree ", &link))
960+
die(_("invalid tree line in commit %s"),
961+
oid_to_hex(oid));
962+
if (buf >= tail)
963+
die(_("truncated commit %s after tree line"),
964+
oid_to_hex(oid));
965+
record_outgoing_link(&link, OBJ_TREE);
966+
967+
while (tail - buf > 7 + the_hash_algo->hexsz &&
968+
starts_with(buf, "parent ")) {
969+
if (parse_outgoing_link_oid(&buf, tail, "parent ",
970+
&link))
971+
die(_("invalid parent line in commit %s"),
972+
oid_to_hex(oid));
973+
if (buf >= tail)
974+
die(_("truncated commit %s after parent line"),
975+
oid_to_hex(oid));
976+
if (!graft ||
977+
(graft->nr_parent >= 0 && grafts_keep_true_parents))
978+
record_outgoing_link(&link, OBJ_COMMIT);
979+
}
980+
if (graft)
981+
for (i = 0; i < graft->nr_parent; i++)
982+
record_outgoing_link(&graft->parent[i],
983+
OBJ_COMMIT);
984+
} else if (type == OBJ_TAG) {
985+
struct object_id link;
986+
const char *line_end;
987+
enum object_type target_type;
988+
989+
if (size < the_hash_algo->hexsz + 24 ||
990+
parse_outgoing_link_oid(&buf, tail, "object ", &link))
991+
die(_("invalid object line in tag %s"), oid_to_hex(oid));
992+
if (!skip_prefix(buf, "type ", &buf) ||
993+
!(line_end = memchr(buf, '\n', tail - buf)))
994+
die(_("invalid type line in tag %s"), oid_to_hex(oid));
995+
target_type = type_from_string_gently(buf, line_end - buf, 1);
996+
if (target_type < 0)
997+
die(_("invalid type line in tag %s"), oid_to_hex(oid));
998+
buf = line_end + 1;
999+
if (buf + 4 >= tail || !skip_prefix(buf, "tag ", &buf) ||
1000+
!memchr(buf, '\n', tail - buf))
1001+
die(_("invalid tag name line in tag %s"),
1002+
oid_to_hex(oid));
1003+
record_outgoing_link(&link, target_type);
1004+
}
8531005
}
8541006

8551007
static void do_record_outgoing_links(struct object *obj)
@@ -871,12 +1023,13 @@ static void do_record_outgoing_links(struct object *obj)
8711023
struct commit *commit = (struct commit *) obj;
8721024
struct commit_list *parents = commit->parents;
8731025

874-
record_outgoing_link(get_commit_tree_oid(commit));
1026+
record_outgoing_link(get_commit_tree_oid(commit), OBJ_TREE);
8751027
for (; parents; parents = parents->next)
876-
record_outgoing_link(&parents->item->object.oid);
1028+
record_outgoing_link(&parents->item->object.oid,
1029+
OBJ_COMMIT);
8771030
} else if (obj->type == OBJ_TAG) {
8781031
struct tag *tag = (struct tag *) obj;
879-
record_outgoing_link(get_tagged_oid(tag));
1032+
record_outgoing_link(get_tagged_oid(tag), tag->tagged->type);
8801033
}
8811034
}
8821035

@@ -925,6 +1078,12 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
9251078
free(has_data);
9261079
}
9271080

1081+
if (record_outgoing_links && !strict && !do_fsck_object) {
1082+
if (type != OBJ_BLOB)
1083+
record_outgoing_links_from_data(data, size, type, oid);
1084+
goto out;
1085+
}
1086+
9281087
if (strict || do_fsck_object || record_outgoing_links) {
9291088
read_lock();
9301089
if (type == OBJ_BLOB) {
@@ -975,6 +1134,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
9751134
read_unlock();
9761135
}
9771136

1137+
out:
9781138
free(new_data);
9791139
}
9801140

@@ -1811,20 +1971,53 @@ static void show_pack_info(int stat_only)
18111971
free(chain_histogram);
18121972
}
18131973

1974+
static const struct object_id *idx_object_oid(size_t pos, const void *table)
1975+
{
1976+
struct pack_idx_entry * const *entries = table;
1977+
1978+
return &entries[pos]->oid;
1979+
}
1980+
1981+
static void validate_outgoing_link_types(struct pack_idx_entry **sorted,
1982+
int nr)
1983+
{
1984+
struct oidmap_iter iter;
1985+
struct outgoing_link *link;
1986+
1987+
oidmap_iter_init(&outgoing_links, &iter);
1988+
while ((link = oidmap_iter_next(&iter))) {
1989+
int pos;
1990+
struct object_entry *actual;
1991+
1992+
if (link->type == OBJ_ANY)
1993+
continue;
1994+
pos = oid_pos(&link->entry.oid, sorted, nr, idx_object_oid);
1995+
if (pos < 0)
1996+
continue;
1997+
actual = container_of(sorted[pos], struct object_entry, idx);
1998+
if (actual->real_type != link->type)
1999+
die(_("object %s is a %s, but was referred to as a %s"),
2000+
oid_to_hex(&link->entry.oid),
2001+
type_name(actual->real_type),
2002+
type_name(link->type));
2003+
}
2004+
}
2005+
18142006
static void repack_local_links(void)
18152007
{
18162008
struct child_process cmd = CHILD_PROCESS_INIT;
18172009
FILE *out;
18182010
struct strbuf line = STRBUF_INIT;
1819-
struct oidset_iter iter;
1820-
struct object_id *oid;
2011+
struct oidmap_iter iter;
2012+
struct outgoing_link *link;
18212013
char *base_name = NULL;
18222014

1823-
if (!oidset_size(&outgoing_links))
2015+
if (!oidmap_get_size(&outgoing_links))
18242016
return;
18252017

1826-
oidset_iter_init(&outgoing_links, &iter);
1827-
while ((oid = oidset_iter_next(&iter))) {
2018+
oidmap_iter_init(&outgoing_links, &iter);
2019+
while ((link = oidmap_iter_next(&iter))) {
2020+
const struct object_id *oid = &link->entry.oid;
18282021
struct odb_source_info source_info;
18292022
struct object_info info = {
18302023
.source_infop = &source_info,
@@ -1919,6 +2112,7 @@ int cmd_index_pack(int argc,
19192112
fsck_options.walk = mark_link;
19202113

19212114
reset_pack_idx_option(&opts);
2115+
oidmap_init(&outgoing_links, 0);
19222116
opts.flags |= WRITE_REV;
19232117
repo_config(the_repository, git_index_pack_config, &opts);
19242118
if (prefix && chdir(prefix))
@@ -2102,6 +2296,7 @@ int cmd_index_pack(int argc,
21022296
idx_objects[i] = &objects[i].idx;
21032297
curr_index = write_idx_file(the_repository, index_name, idx_objects,
21042298
nr_objects, &opts, pack_hash);
2299+
validate_outgoing_link_types(idx_objects, nr_objects);
21052300
if (rev_index)
21062301
curr_rev_index = write_rev_file(the_repository, rev_index_name,
21072302
idx_objects, nr_objects,
@@ -2146,6 +2341,7 @@ int cmd_index_pack(int argc,
21462341
free(curr_rev_index);
21472342

21482343
repack_local_links();
2344+
oidmap_clear(&outgoing_links, 1);
21492345

21502346
/*
21512347
* Let the caller know this pack is not self contained

0 commit comments

Comments
 (0)