Skip to content

Commit ef60735

Browse files
committed
packfile: fix perf regression with many packs
Since 589127c (packfile: move list of packs into the packfile store, 2025-10-30), there is a performance regression when many packfiles need to be loaded: `packfile_store_add_pack()` now calls `packfile_list_remove_internal()` to detect whether the packfile was _already_ in the list, if if so, move it to the end of the list. This function linearly scans the existing list before every insertion. Newly loading N packs therefore has complexity O(N²). In one reported use case (microsoft#970), N equals 37,815 and caused a slow-down of a simple `git rev-parse --short HEAD` (which is regularly executed as part of `GIT_PS1`) from 0.4s to 4.5s. In another, heavily exercised CI scenario, clone times increased from under 2 minutes to over half an hour. Let's fix this by establishing a fast path for known-new packfiles. The keen reader will note that there is currently only a single, "known-new" caller of the `packfile_list_append()` function, and wonder why not simply remove this check whether the packfile already exists in the list? Originally, when above-mentioned commit introduced that logic, there was a second caller in `prepare_midx()`, which would have required that check, but that caller was removed in 6aff1f2 (packfile: always add packfiles to MRU when adding a pack, 2025-10-30). Still, the function is declared in a header file, and to avoid any problems with in-flight or downstream callers, it is safer to extend the signature to be explicit whether or not to skip that check. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 67ad421 commit ef60735

3 files changed

Lines changed: 9 additions & 4 deletions

File tree

packfile.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,12 @@ void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)
103103
list->tail = entry;
104104
}
105105

106-
void packfile_list_append(struct packfile_list *list, struct packed_git *pack)
106+
void packfile_list_append(struct packfile_list *list, struct packed_git *pack,
107+
int is_new)
107108
{
108109
struct packfile_list_entry *entry;
109110

110-
entry = packfile_list_remove_internal(list, pack);
111+
entry = is_new ? NULL : packfile_list_remove_internal(list, pack);
111112
if (!entry) {
112113
entry = xmalloc(sizeof(*entry));
113114
entry->pack = pack;
@@ -860,7 +861,7 @@ void packfile_store_add_pack(struct packfile_store *store,
860861
if (pack->pack_fd != -1)
861862
pack_open_fds++;
862863

863-
packfile_list_append(&store->packs, pack);
864+
packfile_list_append(&store->packs, pack, 1);
864865
strmap_put(&store->packs_by_path, pack->pack_name, pack);
865866
}
866867

packfile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct packfile_list_entry {
6464
void packfile_list_clear(struct packfile_list *list);
6565
void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);
6666
void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);
67-
void packfile_list_append(struct packfile_list *list, struct packed_git *pack);
67+
void packfile_list_append(struct packfile_list *list, struct packed_git *pack, int is_new);
6868

6969
/*
7070
* Find the pack within the "packs" list whose index contains the object

t/perf/p5303-many-packs.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,8 @@ test_perf "load 10,000 packs" '
141141
git rev-parse --verify "HEAD^{commit}"
142142
'
143143

144+
test_perf "abbreviate with 10,000 packs" '
145+
git rev-parse --short HEAD
146+
'
147+
144148
test_done

0 commit comments

Comments
 (0)