speed up alt_odb_usable() with many alternates

With many alternates, the duplicate check in alt_odb_usable()
wastes many cycles doing repeated fspathcmp() on every existing
alternate.  Use a khash to speed up lookups by odb->path.

Since the kh_put_* API uses the supplied key without
duplicating it, we also take advantage of it to replace both
xstrdup() and strbuf_release() in link_alt_odb_entry() with
strbuf_detach() to avoid the allocation and copy.

In a test repository with 50K alternates and each of those 50K
alternates having one alternate each (for a total of 100K total
alternates); this speeds up lookup of a non-existent blob from
over 16 minutes to roughly 2.7 seconds on my busy workstation.

Note: all underlying git object directories were small and
unpacked with only loose objects and no packs.  Having to load
packs increases times significantly.

Signed-off-by: Eric Wong <e@80x24.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Eric Wong 2021-07-07 23:10:15 +00:00 committed by Junio C Hamano
parent 670b81a890
commit cf2dc1c238
5 changed files with 43 additions and 13 deletions

10
dir.c
View File

@ -84,11 +84,21 @@ int fspathcmp(const char *a, const char *b)
return ignore_case ? strcasecmp(a, b) : strcmp(a, b); return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
} }


int fspatheq(const char *a, const char *b)
{
return !fspathcmp(a, b);
}

int fspathncmp(const char *a, const char *b, size_t count) int fspathncmp(const char *a, const char *b, size_t count)
{ {
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
} }


unsigned int fspathhash(const char *str)
{
return ignore_case ? strihash(str) : strhash(str);
}

int git_fnmatch(const struct pathspec_item *item, int git_fnmatch(const struct pathspec_item *item,
const char *pattern, const char *string, const char *pattern, const char *string,
int prefix) int prefix)

2
dir.h
View File

@ -489,7 +489,9 @@ int remove_dir_recursively(struct strbuf *path, int flag);
int remove_path(const char *path); int remove_path(const char *path);


int fspathcmp(const char *a, const char *b); int fspathcmp(const char *a, const char *b);
int fspatheq(const char *a, const char *b);
int fspathncmp(const char *a, const char *b, size_t count); int fspathncmp(const char *a, const char *b, size_t count);
unsigned int fspathhash(const char *str);


/* /*
* The prefix part of pattern must not contains wildcards. * The prefix part of pattern must not contains wildcards.

View File

@ -517,9 +517,9 @@ const char *loose_object_path(struct repository *r, struct strbuf *buf,
*/ */
static int alt_odb_usable(struct raw_object_store *o, static int alt_odb_usable(struct raw_object_store *o,
struct strbuf *path, struct strbuf *path,
const char *normalized_objdir) const char *normalized_objdir, khiter_t *pos)
{ {
struct object_directory *odb; int r;


/* Detect cases where alternate disappeared */ /* Detect cases where alternate disappeared */
if (!is_directory(path->buf)) { if (!is_directory(path->buf)) {
@ -533,14 +533,20 @@ static int alt_odb_usable(struct raw_object_store *o,
* Prevent the common mistake of listing the same * Prevent the common mistake of listing the same
* thing twice, or object directory itself. * thing twice, or object directory itself.
*/ */
for (odb = o->odb; odb; odb = odb->next) { if (!o->odb_by_path) {
if (!fspathcmp(path->buf, odb->path)) khiter_t p;
return 0;
}
if (!fspathcmp(path->buf, normalized_objdir))
return 0;


return 1; o->odb_by_path = kh_init_odb_path_map();
assert(!o->odb->next);
p = kh_put_odb_path_map(o->odb_by_path, o->odb->path, &r);
assert(r == 1); /* never used */
kh_value(o->odb_by_path, p) = o->odb;
}
if (fspatheq(path->buf, normalized_objdir))
return 0;
*pos = kh_put_odb_path_map(o->odb_by_path, path->buf, &r);
/* r: 0 = exists, 1 = never used, 2 = deleted */
return r == 0 ? 0 : 1;
} }


/* /*
@ -566,6 +572,7 @@ static int link_alt_odb_entry(struct repository *r, const char *entry,
{ {
struct object_directory *ent; struct object_directory *ent;
struct strbuf pathbuf = STRBUF_INIT; struct strbuf pathbuf = STRBUF_INIT;
khiter_t pos;


if (!is_absolute_path(entry) && relative_base) { if (!is_absolute_path(entry) && relative_base) {
strbuf_realpath(&pathbuf, relative_base, 1); strbuf_realpath(&pathbuf, relative_base, 1);
@ -587,23 +594,25 @@ static int link_alt_odb_entry(struct repository *r, const char *entry,
while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/')
strbuf_setlen(&pathbuf, pathbuf.len - 1); strbuf_setlen(&pathbuf, pathbuf.len - 1);


if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir)) { if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir, &pos)) {
strbuf_release(&pathbuf); strbuf_release(&pathbuf);
return -1; return -1;
} }


CALLOC_ARRAY(ent, 1); CALLOC_ARRAY(ent, 1);
ent->path = xstrdup(pathbuf.buf); /* pathbuf.buf is already in r->objects->odb_by_path */
ent->path = strbuf_detach(&pathbuf, NULL);


/* add the alternate entry */ /* add the alternate entry */
*r->objects->odb_tail = ent; *r->objects->odb_tail = ent;
r->objects->odb_tail = &(ent->next); r->objects->odb_tail = &(ent->next);
ent->next = NULL; ent->next = NULL;
assert(r->objects->odb_by_path);
kh_value(r->objects->odb_by_path, pos) = ent;


/* recursively add alternates */ /* recursively add alternates */
read_info_alternates(r, pathbuf.buf, depth + 1); read_info_alternates(r, ent->path, depth + 1);


strbuf_release(&pathbuf);
return 0; return 0;
} }



View File

@ -7,6 +7,8 @@
#include "oid-array.h" #include "oid-array.h"
#include "strbuf.h" #include "strbuf.h"
#include "thread-utils.h" #include "thread-utils.h"
#include "khash.h"
#include "dir.h"


struct object_directory { struct object_directory {
struct object_directory *next; struct object_directory *next;
@ -30,6 +32,9 @@ struct object_directory {
char *path; char *path;
}; };


KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
struct object_directory *, 1, fspathhash, fspatheq);

void prepare_alt_odb(struct repository *r); void prepare_alt_odb(struct repository *r);
char *compute_alternate_path(const char *path, struct strbuf *err); char *compute_alternate_path(const char *path, struct strbuf *err);
typedef int alt_odb_fn(struct object_directory *, void *); typedef int alt_odb_fn(struct object_directory *, void *);
@ -116,6 +121,8 @@ struct raw_object_store {
*/ */
struct object_directory *odb; struct object_directory *odb;
struct object_directory **odb_tail; struct object_directory **odb_tail;
kh_odb_path_map_t *odb_by_path;

int loaded_alternates; int loaded_alternates;


/* /*

View File

@ -511,6 +511,8 @@ static void free_object_directories(struct raw_object_store *o)
free_object_directory(o->odb); free_object_directory(o->odb);
o->odb = next; o->odb = next;
} }
kh_destroy_odb_path_map(o->odb_by_path);
o->odb_by_path = NULL;
} }


void raw_object_store_clear(struct raw_object_store *o) void raw_object_store_clear(struct raw_object_store *o)