git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC/PATCHv7 03/22] Speed up git notes lookup

From
Johan Herland <johan@herland.net>
Date
Oct 9, 2009, 10:21 UTC
Message-ID
<1255083738-23263-5-git-send-email-johan@herland.net>
In-Reply-To
<1255083738-23263-1-git-send-email-johan@herland.net>
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>

To avoid looking up each and every commit in the notes ref's tree object, which is very expensive, speed things up by slurping the tree object's contents into a hash_map.

The idea for the hashmap singleton is from David Reiss, initial benchmarking by Jeff King.

Note: the implementation allows for arbitrary entries in the notes
tree object, ignoring those that do not reference a valid object.  This
allows you to annotate arbitrary branches, or objects.
This patch has been improved by the following contributions:
- Junio C Hamano: fixed an obvious error in initialize_hash_map()
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Johan Herland <johan@herland.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 notes.c |  112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 102 insertions(+), 10 deletions(-)
diff --git a/notes.c b/notes.c
index 66379ff..2b66723 100644
--- a/notes.c
+++ b/notes.c
@@ -4,15 +4,112 @@
 #include "refs.h"
 #include "utf8.h"
 #include "strbuf.h"
+#include "tree-walk.h"
+
+struct entry {
+	unsigned char commit_sha1[20];
+	unsigned char notes_sha1[20];
+};
+
+struct hash_map {
+	struct entry *entries;
+	off_t count, size;
+};
 
 static int initialized;
+static struct hash_map hash_map;
+
+static int hash_index(struct hash_map *map, const unsigned char *sha1)
+{
+	int i = ((*(unsigned int *)sha1) % map->size);
+
+	for (;;) {
+		unsigned char *current = map->entries[i].commit_sha1;
+
+		if (!hashcmp(sha1, current))
+			return i;
+
+		if (is_null_sha1(current))
+			return -1 - i;
+
+		if (++i == map->size)
+			i = 0;
+	}
+}
+
+static void add_entry(const unsigned char *commit_sha1,
+		const unsigned char *notes_sha1)
+{
+	int index;
+
+	if (hash_map.count + 1 > hash_map.size >> 1) {
+		int i, old_size = hash_map.size;
+		struct entry *old = hash_map.entries;
+
+		hash_map.size = old_size ? old_size << 1 : 64;
+		hash_map.entries = (struct entry *)
+			xcalloc(sizeof(struct entry), hash_map.size);
+
+		for (i = 0; i < old_size; i++)
+			if (!is_null_sha1(old[i].commit_sha1)) {
+				index = -1 - hash_index(&hash_map,
+						old[i].commit_sha1);
+				memcpy(hash_map.entries + index, old + i,
+					sizeof(struct entry));
+			}
+		free(old);
+	}
+
+	index = hash_index(&hash_map, commit_sha1);
+	if (index < 0) {
+		index = -1 - index;
+		hash_map.count++;
+	}
+
+	hashcpy(hash_map.entries[index].commit_sha1, commit_sha1);
+	hashcpy(hash_map.entries[index].notes_sha1, notes_sha1);
+}
+
+static void initialize_hash_map(const char *notes_ref_name)
+{
+	unsigned char sha1[20], commit_sha1[20];
+	unsigned mode;
+	struct tree_desc desc;
+	struct name_entry entry;
+	void *buf;
+
+	if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
+	    get_tree_entry(commit_sha1, "", sha1, &mode))
+		return;
+
+	buf = fill_tree_descriptor(&desc, sha1);
+	if (!buf)
+		die("Could not read %s for notes-index", sha1_to_hex(sha1));
+
+	while (tree_entry(&desc, &entry))
+		if (!get_sha1(entry.path, commit_sha1))
+			add_entry(commit_sha1, entry.sha1);
+	free(buf);
+}
+
+static unsigned char *lookup_notes(const unsigned char *commit_sha1)
+{
+	int index;
+
+	if (!hash_map.size)
+		return NULL;
+
+	index = hash_index(&hash_map, commit_sha1);
+	if (index < 0)
+		return NULL;
+	return hash_map.entries[index].notes_sha1;
+}
 
 void get_commit_notes(const struct commit *commit, struct strbuf *sb,
 		const char *output_encoding)
 {
 	static const char utf8[] = "utf-8";
-	struct strbuf name = STRBUF_INIT;
-	unsigned char sha1[20];
+	unsigned char *sha1;
 	char *msg, *msg_p;
 	unsigned long linelen, msglen;
 	enum object_type type;
@@ -23,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,
 			notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
 		else if (!notes_ref_name)
 			notes_ref_name = GIT_NOTES_DEFAULT_REF;
-		if (notes_ref_name && read_ref(notes_ref_name, sha1))
-			notes_ref_name = NULL;
+		initialize_hash_map(notes_ref_name);
 		initialized = 1;
 	}
 
-	if (!notes_ref_name)
-		return;
-
-	strbuf_addf(&name, "%s:%s", notes_ref_name,
-			sha1_to_hex(commit->object.sha1));
-	if (get_sha1(name.buf, sha1))
+	sha1 = lookup_notes(commit->object.sha1);
+	if (!sha1)
 		return;
 
 	if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||
-- 
1.6.4.304.g1365c.dirty
Previous: Johan HerlandNext: Johan Herland
Message 6 of 33 in “[RFC/PATCHv7 00/22] git notes”
  1. Johan HerlandOct 9, 2009
  2. Johan HerlandOct 9, 2009
  3. Johan HerlandOct 9, 2009
  4. 01/22 Introduce commit notesJohan Herland, Oct 9, 2009
  5. 02/22 Add a script to edit/inspect notesJohan Herland, Oct 9, 2009
  6. 03/22 Speed up git notes lookupJohan Herland, Oct 9, 2009
  7. 04/22 Add an expensive test for git-notesJohan Herland, Oct 9, 2009
  8. 05/22 Teach "-m <msg>" and "-F <file>" to "git notes edit"Johan Herland, Oct 9, 2009
  9. 06/22 fast-import: Add support for importing commit notesJohan Herland, Oct 9, 2009
  10. [RFC] fast-import: notemodify (N) commandJonathan Nieder, Jan 31, 2011
  11. Sverre RabbelierJan 31, 2011
  12. Jonathan NiederJan 31, 2011
  13. Sverre RabbelierJan 31, 2011
  14. Sam VilainJan 31, 2011
  15. Sverre RabbelierFeb 1, 2011
  16. 07/22 t3302-notes-index-expensive: Speed up create_repo()Johan Herland, Oct 9, 2009
  17. 08/22 Add flags to get_commit_notes() to control the format of the note stringJohan Herland, Oct 9, 2009
  18. 09/22 Add '%N'-format for pretty-printing commit notesJohan Herland, Oct 9, 2009
  19. 10/22 Teach notes code to free its internal data structures on requestJohan Herland, Oct 9, 2009
  20. 11/22 Teach the notes lookup code to parse notes trees with various fanout schemesJohan Herland, Oct 9, 2009
  21. 12/22 Add selftests verifying that we can parse notes trees with various fanoutsJohan Herland, Oct 9, 2009
  22. 13/22 Refactor notes code to concatenate multiple notes annotating the same objectJohan Herland, Oct 9, 2009
  23. 14/22 Add selftests verifying concatenation of multiple notes for the same commitJohan Herland, Oct 9, 2009
  24. 15/22 Notes API: get_commit_notes() -> format_note() + remove the commit restrictionJohan Herland, Oct 9, 2009
  25. 16/22 Notes API: init_notes(): Initialize the notes tree from the given notes refJohan Herland, Oct 9, 2009
  26. 17/22 Notes API: add_note(): Add note objects to the internal notes tree structureJohan Herland, Oct 9, 2009
  27. 18/22 Notes API: get_note(): Return the note annotating the given objectJohan Herland, Oct 9, 2009
  28. 19/22 Notes API: for_each_note(): Traverse the entire notes tree with a callbackJohan Herland, Oct 9, 2009
  29. 20/22 Notes API: Allow multiple concurrent notes trees with new struct notes_treeJohan Herland, Oct 9, 2009
  30. 21/22 Refactor notes concatenation into a flexible interface for combining notesJohan Herland, Oct 9, 2009
  31. 22/22 fast-import: Proper notes tree manipulation using the notes APIJohan Herland, Oct 9, 2009
  32. Shawn O. PearceOct 9, 2009
  33. Johan HerlandNov 20, 2009

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.