git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: Jeff King <peff@peff.net>
To: "René Scharfe" <l.s.r@web.de>
Cc: git@vger.kernel.org
Subject: Re: fast-import's hash table is slow
Date: Wed, 1 Apr 2020 06:24:35 -0400	[thread overview]
Message-ID: <20200401102435.GD60227@coredump.intra.peff.net> (raw)
In-Reply-To: <c0398484-15f4-e5c2-d229-82037094417c@web.de>

On Wed, Apr 01, 2020 at 01:21:08AM +0200, René Scharfe wrote:

> >> +	pos = kh_put_object_entry_set(&object_table, (struct object_entry *)oid, &was_empty);
> >
> > Now this looks illegal.  khash is surely reading a full object_entry from oid,
> > which only is a mere object_id, no?
> 
> No, it's a set of pointers, and khash only accesses the referenced objects
> via the hash and comparison functions.
> 
> Storing the objects directly in the set and getting rid of new_object()
> could improve performance due to reduced dereferencing overhead -- or
> make it slower because more data has to be copied when the hashmap needs
> to grow.  Worth a shot.  Later.

Yeah. I tried that, too, but it caused tons of test failures. Quite
possibly just a bug in my patch, which I did as quickly as possible. But
I think it performed about the same. Here it is for reference (though
you may be better off to start from scratch).

Note the "this is OK to cast from oid to object_entry" comment is
leftover from the earlier attempt, but it probably _isn't_ OK. Even
though we don't do anything actionable on the non-oid bytes, they do get
passed by value, which could mean reading random bytes.

---
diff --git a/fast-import.c b/fast-import.c
index 202dda11a6..5a1b451971 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -39,18 +39,24 @@
 
 struct object_entry {
 	struct pack_idx_entry idx;
-	struct object_entry *next;
 	uint32_t type : TYPE_BITS,
 		pack_id : PACK_ID_BITS,
 		depth : DEPTH_BITS;
 };
 
-struct object_entry_pool {
-	struct object_entry_pool *next_pool;
-	struct object_entry *next_free;
-	struct object_entry *end;
-	struct object_entry entries[FLEX_ARRAY]; /* more */
-};
+static inline unsigned int object_entry_hash(struct object_entry oe)
+{
+	return oidhash(&oe.idx.oid);
+}
+
+static inline int object_entry_equal(struct object_entry a,
+				     struct object_entry b)
+{
+	return oideq(&a.idx.oid, &b.idx.oid);
+}
+
+KHASH_INIT(object_entry_set, struct object_entry, int, 0,
+	   object_entry_hash, object_entry_equal);
 
 struct mark_set {
 	union {
@@ -176,9 +182,7 @@ static struct packed_git **all_packs;
 static off_t pack_size;
 
 /* Table of objects we've written. */
-static unsigned int object_entry_alloc = 5000;
-static struct object_entry_pool *blocks;
-static struct object_entry *object_table[1 << 16];
+static kh_object_entry_set_t object_table;
 static struct mark_set *marks;
 static const char *export_marks_file;
 static const char *import_marks_file;
@@ -428,71 +432,44 @@ static void set_checkpoint_signal(void)
 
 #endif
 
-static void alloc_objects(unsigned int cnt)
-{
-	struct object_entry_pool *b;
-
-	b = xmalloc(sizeof(struct object_entry_pool)
-		+ cnt * sizeof(struct object_entry));
-	b->next_pool = blocks;
-	b->next_free = b->entries;
-	b->end = b->entries + cnt;
-	blocks = b;
-	alloc_count += cnt;
-}
-
-static struct object_entry *new_object(struct object_id *oid)
-{
-	struct object_entry *e;
-
-	if (blocks->next_free == blocks->end)
-		alloc_objects(object_entry_alloc);
-
-	e = blocks->next_free++;
-	oidcpy(&e->idx.oid, oid);
-	return e;
-}
-
 static struct object_entry *find_object(struct object_id *oid)
 {
-	unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-	struct object_entry *e;
-	for (e = object_table[h]; e; e = e->next)
-		if (oideq(oid, &e->idx.oid))
-			return e;
+	/*
+	 * this cast works because we only look at the oid part of the entry,
+	 * and it comes first in the struct
+	 */
+	khiter_t pos = kh_get_object_entry_set(&object_table,
+					       *(struct object_entry *)oid);
+	if (pos != kh_end(&object_table))
+		return &kh_key(&object_table, pos);
 	return NULL;
 }
 
 static struct object_entry *insert_object(struct object_id *oid)
 {
-	unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-	struct object_entry *e = object_table[h];
+	struct object_entry e;
+	int was_empty;
+	khiter_t pos;
 
-	while (e) {
-		if (oideq(oid, &e->idx.oid))
-			return e;
-		e = e->next;
-	}
+	oidcpy(&e.idx.oid, oid);
+	e.idx.offset = 0;
+	pos = kh_put_object_entry_set(&object_table, e, &was_empty);
 
-	e = new_object(oid);
-	e->next = object_table[h];
-	e->idx.offset = 0;
-	object_table[h] = e;
-	return e;
+	return &kh_key(&object_table, pos);
 }
 
 static void invalidate_pack_id(unsigned int id)
 {
-	unsigned int h;
 	unsigned long lu;
 	struct tag *t;
+	khiter_t iter;
 
-	for (h = 0; h < ARRAY_SIZE(object_table); h++) {
-		struct object_entry *e;
-
-		for (e = object_table[h]; e; e = e->next)
+	for (iter = kh_begin(&object_table); iter != kh_end(&object_table); iter++) {
+		if (kh_exist(&object_table, iter)) {
+			struct object_entry *e = &kh_key(&object_table, iter);
 			if (e->pack_id == id)
 				e->pack_id = MAX_PACK_ID;
+		}
 	}
 
 	for (lu = 0; lu < branch_table_sz; lu++) {
@@ -766,15 +743,18 @@ static const char *create_index(void)
 	const char *tmpfile;
 	struct pack_idx_entry **idx, **c, **last;
 	struct object_entry *e;
-	struct object_entry_pool *o;
+	khiter_t iter;
 
 	/* Build the table of object IDs. */
 	ALLOC_ARRAY(idx, object_count);
 	c = idx;
-	for (o = blocks; o; o = o->next_pool)
-		for (e = o->next_free; e-- != o->entries;)
+	for (iter = kh_begin(&object_table); iter != kh_end(&object_table); iter++) {
+		if (kh_exist(&object_table, iter)) {
+			e = &kh_key(&object_table, iter);
 			if (pack_id == e->pack_id)
 				*c++ = &e->idx;
+		}
+	}
 	last = idx + object_count;
 	if (c != last)
 		die("internal consistency error creating the index");
@@ -3504,7 +3484,6 @@ int cmd_main(int argc, const char **argv)
 	reset_pack_idx_option(&pack_idx_opts);
 	git_pack_config();
 
-	alloc_objects(object_entry_alloc);
 	strbuf_init(&command_buf, 0);
 	atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
 	branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));

  reply	other threads:[~2020-04-01 10:24 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-03-31  9:45 fast-import's hash table is slow Jeff King
2020-03-31 19:14 ` René Scharfe
2020-03-31 23:21   ` René Scharfe
2020-04-01 10:24     ` Jeff King [this message]
2020-04-02 18:40       ` René Scharfe
2020-04-03 12:14         ` Jeff King
2020-04-01 10:35   ` Jeff King
2020-04-01 11:16     ` Jeff King
2020-04-02 18:40       ` René Scharfe
2020-04-03 12:12         ` Jeff King
2020-04-03 18:53           ` René Scharfe
2020-04-03 19:01             ` Jeff King

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200401102435.GD60227@coredump.intra.peff.net \
    --to=peff@peff.net \
    --cc=git@vger.kernel.org \
    --cc=l.s.r@web.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).