mirror of
https://github.com/git/git.git
synced 2026-09-26 16:02:01 +02:00
Since589127caa7(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, and 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 (https://github.com/microsoft/git/issues/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. 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 in6aff1f25a0(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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
88 lines
1.8 KiB
C
88 lines
1.8 KiB
C
#include "git-compat-util.h"
|
|
#include "packfile.h"
|
|
#include "packfile-list.h"
|
|
|
|
void packfile_list_clear(struct packfile_list *list)
|
|
{
|
|
struct packfile_list_entry *e, *next;
|
|
|
|
for (e = list->head; e; e = next) {
|
|
next = e->next;
|
|
free(e);
|
|
}
|
|
|
|
list->head = list->tail = NULL;
|
|
}
|
|
|
|
static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,
|
|
struct packed_git *pack)
|
|
{
|
|
struct packfile_list_entry *e, *prev;
|
|
|
|
for (e = list->head, prev = NULL; e; prev = e, e = e->next) {
|
|
if (e->pack != pack)
|
|
continue;
|
|
|
|
if (prev)
|
|
prev->next = e->next;
|
|
if (list->head == e)
|
|
list->head = e->next;
|
|
if (list->tail == e)
|
|
list->tail = prev;
|
|
|
|
return e;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)
|
|
{
|
|
free(packfile_list_remove_internal(list, pack));
|
|
}
|
|
|
|
void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)
|
|
{
|
|
struct packfile_list_entry *entry;
|
|
|
|
entry = packfile_list_remove_internal(list, pack);
|
|
if (!entry) {
|
|
entry = xmalloc(sizeof(*entry));
|
|
entry->pack = pack;
|
|
}
|
|
entry->next = list->head;
|
|
|
|
list->head = entry;
|
|
if (!list->tail)
|
|
list->tail = entry;
|
|
}
|
|
|
|
void packfile_list_append(struct packfile_list *list, struct packed_git *pack,
|
|
int skip_dup_check)
|
|
{
|
|
struct packfile_list_entry *entry;
|
|
|
|
entry = skip_dup_check ? NULL : packfile_list_remove_internal(list, pack);
|
|
if (!entry) {
|
|
entry = xmalloc(sizeof(*entry));
|
|
entry->pack = pack;
|
|
}
|
|
entry->next = NULL;
|
|
|
|
if (list->tail) {
|
|
list->tail->next = entry;
|
|
list->tail = entry;
|
|
} else {
|
|
list->head = list->tail = entry;
|
|
}
|
|
}
|
|
|
|
struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,
|
|
const struct object_id *oid)
|
|
{
|
|
for (; packs; packs = packs->next)
|
|
if (find_pack_entry_one(oid, packs->pack))
|
|
return packs->pack;
|
|
return NULL;
|
|
}
|