Speed up git notes lookup
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>
Johannes Schindelin committed
Oct 9, 2009 at 12:21 UTC
fd53c9eb445815696bf84c4701b9af73b5d7f50d
1 file changed
+102
-10
notes.c
+102
-10
@@ -4,15 +4,112 @@
4
#include "refs.h"
5
#include "utf8.h"
6
#include "strbuf.h"
7
+#include "tree-walk.h"
8
+
9
+struct entry {
10
+ unsigned char commit_sha1[20];
11
+ unsigned char notes_sha1[20];
12
+};
13
+
14
+struct hash_map {
15
+ struct entry *entries;
16
+ off_t count, size;
17
+};
18
19
static int initialized;
20
+static struct hash_map hash_map;
21
+
22
+static int hash_index(struct hash_map *map, const unsigned char *sha1)
23
+{
24
+ int i = ((*(unsigned int *)sha1) % map->size);
25
+
26
+ for (;;) {
27
+ unsigned char *current = map->entries[i].commit_sha1;
28
+
29
+ if (!hashcmp(sha1, current))
30
+ return i;
31
+
32
+ if (is_null_sha1(current))
33
+ return -1 - i;
34
+
35
+ if (++i == map->size)
36
+ i = 0;
37
+ }
38
+}
39
+
40
+static void add_entry(const unsigned char *commit_sha1,
41
+ const unsigned char *notes_sha1)
42
+{
43
+ int index;
44
+
45
+ if (hash_map.count + 1 > hash_map.size >> 1) {
46
+ int i, old_size = hash_map.size;
47
+ struct entry *old = hash_map.entries;
48
+
49
+ hash_map.size = old_size ? old_size << 1 : 64;
50
+ hash_map.entries = (struct entry *)
51
+ xcalloc(sizeof(struct entry), hash_map.size);
52
+
53
+ for (i = 0; i < old_size; i++)
54
+ if (!is_null_sha1(old[i].commit_sha1)) {
55
+ index = -1 - hash_index(&hash_map,
56
+ old[i].commit_sha1);
57
+ memcpy(hash_map.entries + index, old + i,
58
+ sizeof(struct entry));
59
+ }
60
+ free(old);
61
+ }
62
+
63
+ index = hash_index(&hash_map, commit_sha1);
64
+ if (index < 0) {
65
+ index = -1 - index;
66
+ hash_map.count++;
67
+ }
68
+
69
+ hashcpy(hash_map.entries[index].commit_sha1, commit_sha1);
70
+ hashcpy(hash_map.entries[index].notes_sha1, notes_sha1);
71
+}
72
+
73
+static void initialize_hash_map(const char *notes_ref_name)
74
+{
75
+ unsigned char sha1[20], commit_sha1[20];
76
+ unsigned mode;
77
+ struct tree_desc desc;
78
+ struct name_entry entry;
79
+ void *buf;
80
+
81
+ if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
82
+ get_tree_entry(commit_sha1, "", sha1, &mode))
83
+ return;
84
+
85
+ buf = fill_tree_descriptor(&desc, sha1);
86
+ if (!buf)
87
+ die("Could not read %s for notes-index", sha1_to_hex(sha1));
88
+
89
+ while (tree_entry(&desc, &entry))
90
+ if (!get_sha1(entry.path, commit_sha1))
91
+ add_entry(commit_sha1, entry.sha1);
92
+ free(buf);
93
+}
94
+
95
+static unsigned char *lookup_notes(const unsigned char *commit_sha1)
96
+{
97
+ int index;
98
+
99
+ if (!hash_map.size)
100
+ return NULL;
101
+
102
+ index = hash_index(&hash_map, commit_sha1);
103
+ if (index < 0)
104
+ return NULL;
105
+ return hash_map.entries[index].notes_sha1;
106
+}
107
108
void get_commit_notes(const struct commit *commit, struct strbuf *sb,
109
const char *output_encoding)
110
{
111
static const char utf8[] = "utf-8";
14
- struct strbuf name = STRBUF_INIT;
15
- unsigned char sha1[20];
112
+ unsigned char *sha1;
113
char *msg, *msg_p;
114
unsigned long linelen, msglen;
115
enum object_type type;
@@ -23,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,
120
notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
121
else if (!notes_ref_name)
122
notes_ref_name = GIT_NOTES_DEFAULT_REF;
26
- if (notes_ref_name && read_ref(notes_ref_name, sha1))
27
- notes_ref_name = NULL;
123
+ initialize_hash_map(notes_ref_name);
124
initialized = 1;
125
}
126
31
- if (!notes_ref_name)
32
- return;
33
-
34
- strbuf_addf(&name, "%s:%s", notes_ref_name,
35
- sha1_to_hex(commit->object.sha1));
36
- if (get_sha1(name.buf, sha1))
127
+ sha1 = lookup_notes(commit->object.sha1);
128
+ if (!sha1)
129
return;
130
131
if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||